ThirdPartyController.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <?php
  2. namespace addons\TinyShop\api\modules\v1\controllers;
  3. use Yii;
  4. use yii\helpers\Json;
  5. use yii\web\UnprocessableEntityHttpException;
  6. use api\controllers\OnAuthController;
  7. use api\modules\v1\forms\MiniProgramLoginForm;
  8. use api\modules\v1\forms\MiniProgramDecodeForm;
  9. use api\modules\v1\forms\ByteDanceMicroLoginForm;
  10. use common\models\member\Auth;
  11. use common\helpers\ResultHelper;
  12. use common\enums\WhetherEnum;
  13. use common\models\member\Member;
  14. use common\enums\GenderEnum;
  15. use common\helpers\StringHelper;
  16. use common\helpers\FileHelper;
  17. use common\helpers\RegularHelper;
  18. use common\enums\MemberTypeEnum;
  19. use common\enums\StatusEnum;
  20. use addons\TinyShop\api\modules\v1\forms\AppleLoginForm;
  21. use addons\TinyShop\common\enums\AccessTokenGroupEnum;
  22. use League\Flysystem\Adapter\Local;
  23. use League\Flysystem\Filesystem;
  24. /**
  25. * 第三方授权登录
  26. *
  27. * Class ThirdPartyController
  28. * @package addons\TinyShop\api\modules\v1\controllers
  29. * @author jianyan74 <751393839@qq.com>
  30. */
  31. class ThirdPartyController extends OnAuthController
  32. {
  33. public $modelClass = '';
  34. /**
  35. * 不用进行登录验证的方法
  36. * 例如: ['index', 'update', 'create', 'view', 'delete']
  37. * 默认全部需要验证
  38. *
  39. * @var array
  40. */
  41. protected $authOptional = [
  42. 'wechat',
  43. 'apple',
  44. 'wechat-mp',
  45. 'wechat-mini',
  46. 'wechat-mini-mobile',
  47. 'byte-dance-mini',
  48. 'wechat-mp-js-sdk',
  49. 'wechat-mini-qr-code',
  50. ];
  51. /**
  52. * @param $action
  53. * @return bool
  54. * @throws UnprocessableEntityHttpException
  55. * @throws \yii\base\InvalidConfigException
  56. * @throws \yii\web\BadRequestHttpException
  57. * @throws \yii\web\ForbiddenHttpException
  58. */
  59. public function beforeAction($action)
  60. {
  61. $config = Yii::$app->tinyShopService->config->setting();
  62. switch ($action->id) {
  63. case 'wechat' :
  64. case 'apple' :
  65. case 'wechat-mp' :
  66. case 'wechat-mini' :
  67. case 'byte-dance-mini' :
  68. if ($config['member_login'] == StatusEnum::DISABLED) {
  69. throw new UnprocessableEntityHttpException('授权登录已关闭');
  70. }
  71. break;
  72. }
  73. return parent::beforeAction($action);
  74. }
  75. /**
  76. * 微信公众号登录
  77. *
  78. * @return array|mixed
  79. * @throws \yii\base\Exception
  80. */
  81. public function actionWechatMp()
  82. {
  83. if (empty($code = Yii::$app->request->get('code'))) {
  84. return ResultHelper::json(422, '请传递 code');
  85. }
  86. $user = Yii::$app->wechat->app->oauth->userFromCode($code);
  87. // 用户信息
  88. $original = $user->getRaw();
  89. $authModel = new Auth();
  90. $authModel->unionid = $original['unionid'] ?? '';
  91. $authModel->oauth_client = AccessTokenGroupEnum::relevance(AccessTokenGroupEnum::WECHAT_MP);
  92. $authModel->oauth_client_user_id = $user->getId();
  93. $authModel->nickname = $user->getNickname();
  94. $authModel->head_portrait = $user->getAvatar();
  95. return $this->getMember($authModel, AccessTokenGroupEnum::WECHAT_MP);
  96. }
  97. /**
  98. * 微信小程序登录
  99. *
  100. * @return array|mixed
  101. * @throws \EasyWeChat\Kernel\Exceptions\DecryptException
  102. * @throws \yii\base\Exception
  103. * @throws \Exception
  104. */
  105. public function actionWechatMini()
  106. {
  107. $model = new MiniProgramLoginForm();
  108. $model->attributes = Yii::$app->request->post();
  109. if (!$model->validate()) {
  110. return ResultHelper::json(422, $this->getError($model));
  111. }
  112. $user = $model->getUser();
  113. if ($user['nickName'] == '微信用户') {
  114. $user['nickName'] = StringHelper::random(6);
  115. }
  116. $authModel = new Auth();
  117. $authModel->unionid = $model->getUnionId();
  118. $authModel->oauth_client = AccessTokenGroupEnum::relevance(AccessTokenGroupEnum::WECHAT_MINI);
  119. $authModel->oauth_client_user_id = $model->getOpenid();
  120. $authModel->nickname = $user['nickName'];
  121. $authModel->head_portrait = $user['avatarUrl'];
  122. $authModel->gender = $user['gender'];
  123. return $this->getMember($authModel, AccessTokenGroupEnum::WECHAT_MINI);
  124. }
  125. /**
  126. * 微信小程序手机号码绑定
  127. *
  128. * @return array|mixed
  129. * @throws \EasyWeChat\Kernel\Exceptions\DecryptException
  130. * @throws \yii\base\Exception
  131. * @throws \Exception
  132. */
  133. public function actionWechatMiniMobile()
  134. {
  135. $model = new MiniProgramDecodeForm();
  136. $model->attributes = Yii::$app->request->post();
  137. if (!$model->validate()) {
  138. return ResultHelper::json(422, $this->getError($model));
  139. }
  140. $info = $model->getUser();
  141. if (!Yii::$app->user->isGuest) {
  142. // 判断手机号是否已绑定用户
  143. if (Yii::$app->services->member->findByCondition(['mobile' => $info['purePhoneNumber'], 'type' => MemberTypeEnum::MEMBER])) {
  144. throw new UnprocessableEntityHttpException('该手机号码已绑定账号');
  145. }
  146. // 更新用户手机号码
  147. Member::updateAll(['mobile' => $info['purePhoneNumber']], ['id' => Yii::$app->user->identity->member_id]);
  148. }
  149. return $info;
  150. }
  151. /**
  152. * 字节跳动小程序登录
  153. *
  154. * @return array|mixed
  155. * @throws \EasyWeChat\Kernel\Exceptions\DecryptException
  156. * @throws \yii\base\Exception
  157. * @throws \Exception
  158. */
  159. public function actionByteDanceMini()
  160. {
  161. $model = new ByteDanceMicroLoginForm();
  162. $model->attributes = Yii::$app->request->post();
  163. if (!$model->validate()) {
  164. return ResultHelper::json(422, $this->getError($model));
  165. }
  166. $user = $model->getUser();
  167. $authModel = new Auth();
  168. $authModel->unionid = $model->getUnionId();
  169. $authModel->oauth_client = AccessTokenGroupEnum::relevance(AccessTokenGroupEnum::BYTEDANCE_MINI);
  170. $authModel->oauth_client_user_id = $model->getOpenid();
  171. $authModel->nickname = $user['nickName'];
  172. $authModel->head_portrait = $user['avatarUrl'];
  173. $authModel->gender = $user['gender'];
  174. return $this->getMember($authModel, AccessTokenGroupEnum::BYTEDANCE_MINI);
  175. }
  176. /**
  177. * apple 登录
  178. *
  179. * @return array|mixed
  180. */
  181. public function actionApple()
  182. {
  183. $model = new AppleLoginForm();
  184. $model->attributes = Yii::$app->request->post();
  185. if (!$model->validate()) {
  186. return ResultHelper::json(422, $this->getError($model));
  187. }
  188. try {
  189. // Yii::$app->services->extendOpenPlatform->apple($model->user, $model->identityToken);
  190. // $familyName = $model->familyName['familyName'] ?? '';
  191. // $giveName = $model->familyName['giveName'] ?? '';
  192. $nickname = StringHelper::random(5) . '_' . StringHelper::random(4, true);
  193. $authModel = new Auth();
  194. $authModel->oauth_client = AccessTokenGroupEnum::relevance(AccessTokenGroupEnum::APPLE);;
  195. $authModel->oauth_client_user_id = $model->user;
  196. $authModel->nickname = $nickname;
  197. $authModel->gender = GenderEnum::UNKNOWN;
  198. return $this->getMember($authModel, AccessTokenGroupEnum::IOS);
  199. } catch (\Exception $e) {
  200. if (YII_DEBUG) {
  201. return ResultHelper::json(422, $e->getMessage());
  202. }
  203. return ResultHelper::json(422, '用户验证失败请重新授权');
  204. }
  205. }
  206. /**
  207. * 微信开放平台登录
  208. *
  209. * @return array|mixed
  210. * @throws \yii\base\Exception
  211. */
  212. public function actionWechat()
  213. {
  214. if (!($code = Yii::$app->request->get('code'))) {
  215. return ResultHelper::json(422, '请传递 code');
  216. }
  217. if (!($group = Yii::$app->request->get('group')) && !in_array($group, AccessTokenGroupEnum::getMap())) {
  218. return ResultHelper::json(422, '请传递有效的组别');
  219. }
  220. $original = Yii::$app->services->extendOpenPlatform->wechat($code);
  221. $authModel = new Auth();
  222. $authModel->unionid = $original['unionid'] ?? '';
  223. $authModel->oauth_client = AccessTokenGroupEnum::relevance(AccessTokenGroupEnum::WECHAT);;
  224. $authModel->oauth_client_user_id = $original['openid'];
  225. $authModel->nickname = $original['nickname'];
  226. $authModel->head_portrait = $original['headimgurl'];
  227. $authModel->gender = $original['sex'];
  228. return $this->getMember($authModel, $group);
  229. }
  230. /**
  231. * 生成小程序码
  232. *
  233. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  234. * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
  235. */
  236. public function actionWechatMiniQrCode($id, $marketing_id = '', $marketing_type = '', $marketing_product_id = '')
  237. {
  238. if (!($product = Yii::$app->tinyShopService->product->findById($id))) {
  239. return ResultHelper::json(422, '找不到商品');
  240. }
  241. $path = "pages/product/product?id=$id&marketingType=$marketing_type&marketingId=$marketing_id&marketingProductId=$marketing_product_id";
  242. empty($marketing_type) && $marketing_type = 'default';
  243. $prefix = "/mini_program/product/$marketing_type/";
  244. $promoCode = 0;
  245. if (!Yii::$app->user->isGuest) {
  246. $member = Yii::$app->services->member->findById(Yii::$app->user->identity->member_id);
  247. $promoCode = $member['promoter_code'];
  248. $path = $path . '&promoter_code=' . $promoCode;
  249. }
  250. $uploadDrive = new Local(Yii::getAlias('@attachment'), FILE_APPEND);
  251. $filesystem = new Filesystem($uploadDrive);
  252. $directory = Yii::getAlias('@attachment') . $prefix;
  253. FileHelper::mkdirs($directory);
  254. $filename = $id . '_' . $marketing_id . '_' . $marketing_product_id . '_' . $promoCode;
  255. if (!$filesystem->has($prefix . $filename . '.png')) {
  256. // 指定颜色
  257. // $response 成功时为 EasyWeChat\Kernel\Http\StreamResponse 实例,失败时为数组或者你指定的 API 返回格式
  258. $response = Yii::$app->wechat->miniProgram->app_code->get($path, [
  259. 'width' => 300,
  260. // 'line_color' => [
  261. // 'r' => 105,
  262. // 'g' => 166,
  263. // 'b' => 134,
  264. // ],
  265. ]);
  266. $content = $response->getBody()->getContents();
  267. // 保存小程序码到文件
  268. if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
  269. $response->saveAs($directory, $filename . '.png');
  270. }
  271. }
  272. $url = Yii::getAlias('@attachurl') . $prefix . $filename . '.png';
  273. if (!RegularHelper::verify('url', $url)) {
  274. $url = Yii::$app->request->hostInfo . $url;
  275. }
  276. return [
  277. 'url' => $url
  278. ];
  279. }
  280. /*
  281. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  282. * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
  283. * @throws \Psr\SimpleCache\InvalidArgumentException
  284. */
  285. public function actionWechatMpJsSdk()
  286. {
  287. $url = Yii::$app->request->post('url');
  288. $apis = Yii::$app->request->post('jsApiList');
  289. $debug = Yii::$app->request->post('debug', false);
  290. $apis = !empty($apis) ? Json::decode($apis) : [];
  291. Yii::$app->wechat->app->jssdk->setUrl($url);
  292. return Yii::$app->wechat->app->jssdk->buildConfig($apis, $debug, $beta = false, $json = false);
  293. }
  294. /**
  295. * @param Auth $auth
  296. * @return array
  297. */
  298. protected function getMember(Auth $authModel, $group)
  299. {
  300. // 查询授权绑定
  301. $auth = Yii::$app->services->memberAuth->findOauthClient($authModel->oauth_client, $authModel->oauth_client_user_id);
  302. if ($auth && $auth->member) {
  303. return [
  304. 'login' => true,
  305. 'user_info' => $this->getData($auth, $group),
  306. ];
  307. }
  308. // 查询唯一绑定
  309. if ($authModel->unionid && ($auth = Yii::$app->services->memberAuth->findByUnionId($authModel->unionid)) && $auth->member) {
  310. $authModel->member_id = $auth->member->id;
  311. $authModel->head_portrait = $auth->member->head_portrait;
  312. Yii::$app->services->memberAuth->create($authModel->toArray());
  313. return [
  314. 'login' => true,
  315. 'user_info' => $this->getData($auth, $group),
  316. ];
  317. }
  318. // 判断是否强制注册
  319. if ($this->isConstraintRegister() === true) {
  320. return [
  321. 'login' => false,
  322. 'user_info' => $authModel->toArray()
  323. ];
  324. }
  325. $member = $this->createMember($authModel->head_portrait, $authModel->gender, $authModel->nickname, $authModel->oauth_client, Yii::$app->request->get('promoter_code'));
  326. $authModel->member_id = $member['id'];
  327. $authModel->head_portrait = $member['head_portrait'];
  328. $auth = Yii::$app->services->memberAuth->create($authModel->toArray());
  329. return [
  330. 'login' => true,
  331. 'user_info' => $this->getData($auth, $group)
  332. ];
  333. }
  334. /**
  335. * 创建用户
  336. *
  337. * @param $head_portrait
  338. * @param $gender
  339. * @param $nickname
  340. * @param string $promoCode
  341. * @return Member
  342. * @throws UnprocessableEntityHttpException
  343. * @throws \League\Flysystem\FileExistsException
  344. * @throws \League\Flysystem\FileNotFoundException
  345. * @throws \yii\web\NotFoundHttpException
  346. */
  347. protected function createMember($head_portrait, $gender, $nickname, $source, $promoCode = '')
  348. {
  349. // 下载头像
  350. $baseInfo = Yii::$app->services->extendUpload->downloadByUrl($head_portrait);
  351. // 注册新账号
  352. $member = new Member();
  353. $member = $member->loadDefaultValues();
  354. $member->merchant_id = Yii::$app->services->merchant->getNotNullId();
  355. $member->source = $source;
  356. $member->pid = 0;
  357. $member->attributes = [
  358. 'gender' => $gender,
  359. 'nickname' => $nickname,
  360. 'head_portrait' => $baseInfo['url'] ?? '',
  361. ];
  362. $member->save();
  363. return $member;
  364. }
  365. /**
  366. * @param $auth
  367. * @return array
  368. * @throws \yii\base\Exception
  369. */
  370. protected function getData($auth, $group = AccessTokenGroupEnum::ANDROID)
  371. {
  372. $data = Yii::$app->services->apiAccessToken->getAccessToken($auth->member, $group);
  373. // 优惠券数量
  374. $data['couponNum'] = Yii::$app->tinyShopService->marketingCoupon->findCountByMemberId($data['member']['id']);
  375. // 购物车数量
  376. $data['cartNum'] = Yii::$app->tinyShopService->memberCartItem->findCountByMemberId($data['member']['id']);
  377. // 订单数量统计
  378. $data['orderNum'] = Yii::$app->tinyShopService->order->getOrderStatusCountByMemberId($data['member']['id']);
  379. // 分销商
  380. $data['promoter'] = '';
  381. $data['promoterAccount'] = '';
  382. // 记录登录时间次数
  383. Yii::$app->services->member->lastLogin($auth->member);
  384. return $data;
  385. }
  386. /**
  387. * @param $promoCode
  388. * @return array|bool|\yii\db\ActiveRecord
  389. * @throws UnprocessableEntityHttpException
  390. */
  391. protected function getParent($promoCode)
  392. {
  393. if (empty($promoCode) || $promoCode == 'undefined') {
  394. return false;
  395. }
  396. $parent = Yii::$app->services->member->findByPromoterCode($promoCode);
  397. if (!$parent) {
  398. throw new UnprocessableEntityHttpException('找不到推广员');
  399. }
  400. return $parent;
  401. }
  402. /**
  403. * 强制注册
  404. *
  405. * @return bool
  406. */
  407. protected function isConstraintRegister()
  408. {
  409. // 判断非强制性登录
  410. $setting = Yii::$app->tinyShopService->config->setting();
  411. if ($setting->member_third_party_binding_type == WhetherEnum::ENABLED) {
  412. return false;
  413. }
  414. return true;
  415. }
  416. /**
  417. * 权限验证
  418. *
  419. * @param string $action 当前的方法
  420. * @param null $model 当前的模型类
  421. * @param array $params $_GET变量
  422. * @throws \yii\web\BadRequestHttpException
  423. */
  424. public function checkAccess($action, $model = null, $params = [])
  425. {
  426. // 方法名称
  427. if (in_array($action, ['index', 'view', 'update', 'create', 'delete'])) {
  428. throw new \yii\web\BadRequestHttpException('权限不足');
  429. }
  430. }
  431. }
粤ICP备19079148号