MemberOfficeAccountService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * Author: 芸众商城 www.yunzshop.com
  5. * Date: 17/2/22
  6. * Time: 下午4:44
  7. */
  8. namespace app\frontend\modules\member\services;
  9. use app\common\exceptions\MemberErrorMsgException;
  10. use app\common\facades\EasyWeChat;
  11. use app\common\facades\Setting;
  12. use app\common\helpers\Cache;
  13. use app\common\helpers\Client;
  14. use app\common\helpers\Url;
  15. use app\common\models\AccountWechats;
  16. use app\common\models\Member;
  17. use app\common\services\Session;
  18. use app\frontend\modules\member\models\McMappingFansModel;
  19. use app\frontend\modules\member\models\MemberUniqueModel;
  20. use app\frontend\modules\member\models\SubMemberModel;
  21. use Illuminate\Contracts\Encryption\DecryptException;
  22. class MemberOfficeAccountService extends MemberService
  23. {
  24. const LOGIN_TYPE = '1';
  25. public function __construct()
  26. {
  27. }
  28. public function login()
  29. {
  30. $member_id = 0;
  31. $uniacid = \YunShop::app()->uniacid;
  32. $scope = \YunShop::request()->scope ?: 'userinfo'; //scope: base|home|userinfo
  33. $code = \YunShop::request()->code;
  34. if (Setting::get('shop.member')['wechat_login_mode'] == '1') {
  35. return $this->isPhoneLogin($uniacid);
  36. }
  37. $callback = ($_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  38. $wechat_scope = $this->wechatScope();
  39. $config = [
  40. 'oauth' => [
  41. 'scopes' => [$wechat_scope],
  42. 'callback' => $callback,
  43. ]
  44. ];
  45. $app = EasyWeChat::officialAccount($config);
  46. $oauth = $app->oauth;
  47. if (!empty($code)) {
  48. $oauthUser = $oauth->user();
  49. $userinfo = $oauthUser->getOriginal();
  50. $userinfo['access_token'] = $oauthUser->getAccessToken();
  51. $userinfo['expires_in'] = 7200;
  52. $userinfo['refresh_token'] = $oauthUser->getRefreshToken();
  53. \Log::debug('-----EasyWeChat-----', [$userinfo['openid']]);
  54. $user = $app->user->get($userinfo['openid']);
  55. $userinfo = array_merge($user, $userinfo);
  56. //Login
  57. $member_id = $this->memberLogin($userinfo);
  58. if($member_id) event(new \app\common\events\member\MemberLoginEvent($member_id));
  59. Session::set('member_id', $member_id);
  60. setcookie('Yz-Token', encrypt($userinfo['access_token'] . '\t' . ($userinfo['expires_in'] + time()) . '\t' . $userinfo['openid'] . '\t' . $scope), time() + self::TOKEN_EXPIRE);
  61. } else {
  62. $this->_setClientRequestUrl();
  63. $oauth->redirect()->send();
  64. exit;
  65. }
  66. redirect($this->_getClientRequestUrl())->send();
  67. exit;
  68. }
  69. /**
  70. * 获取用户信息
  71. *
  72. * @param $appId
  73. * @param $appSecret
  74. * @param $token
  75. * @return mixed
  76. */
  77. public function getUserInfo($appId, $appSecret, $token)
  78. {
  79. $scope = \YunShop::request()->scope ?: '';
  80. $subscribe = 0;
  81. $share = Setting::get('shop.share');
  82. $user_info = [];
  83. if (is_null($share) || $share['follow'] == 1 || ($share && is_null($share['follow']))) {
  84. $global_access_token_url = $this->_getAccessToken($appId, $appSecret);
  85. $global_token = \Curl::to($global_access_token_url)
  86. ->asJsonResponse(true)
  87. ->get();
  88. $global_userinfo_url = $this->_getInfo($global_token['access_token'], $token['openid']);
  89. $user_info = \Curl::to($global_userinfo_url)
  90. ->asJsonResponse(true)
  91. ->get();
  92. $subscribe = $user_info['subscribe'];
  93. }
  94. if (0 == $subscribe && $scope != 'base') { //未关注拉取不到用户信息
  95. $userinfo_url = $this->_getUserInfoUrl($token['access_token'], $token['openid']);
  96. $user_info = \Curl::to($userinfo_url)
  97. ->asJsonResponse(true)
  98. ->get();
  99. $user_info['subscribe'] = $subscribe;
  100. }
  101. return array_merge($user_info, $token);
  102. }
  103. /**
  104. * 用户验证授权 api
  105. *
  106. * snsapi_userinfo
  107. *
  108. * @param $appId
  109. * @param $url
  110. * @param $state
  111. * @return string
  112. */
  113. private function _getAuthUrl($appId, $url, $state)
  114. {
  115. return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" . $appId . "&redirect_uri=" . urlencode($url) . "&response_type=code&scope=snsapi_userinfo&state={$state}#wechat_redirect";
  116. }
  117. /**
  118. *
  119. * 静默获取用户信息
  120. *
  121. * snsapi_base
  122. *
  123. * @param $appId
  124. * @param $url
  125. * @param $state
  126. * @return string
  127. */
  128. private function _getAuthBaseUrl($appId, $url, $state)
  129. {
  130. return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" . $appId . "&redirect_uri=" . urlencode($url) . "&response_type=code&scope=snsapi_base&state={$state}#wechat_redirect";
  131. }
  132. /**
  133. * 获取token api
  134. *
  135. * @param $appId
  136. * @param $appSecret
  137. * @param $code
  138. * @return string
  139. */
  140. private function _getTokenUrl($appId, $appSecret, $code)
  141. {
  142. return "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" . $appId . "&secret=" . $appSecret . "&code=" . $code . "&grant_type=authorization_code";
  143. }
  144. /**
  145. * 获取用户信息 api
  146. *
  147. * 无需关注
  148. *
  149. * @param $accesstoken
  150. * @param $openid
  151. * @return string
  152. */
  153. private function _getUserInfoUrl($accesstoken, $openid)
  154. {
  155. return "https://api.weixin.qq.com/sns/userinfo?access_token={$accesstoken}&openid={$openid}&lang=zh_CN";
  156. }
  157. /**
  158. * 获取全局ACCESS TOKEN
  159. * @return string
  160. */
  161. private function _getAccessToken($appId, $appSecret)
  162. {
  163. return 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $appId . '&secret=' . $appSecret;
  164. }
  165. /**
  166. * 获取用户信息
  167. *
  168. * 需要关注
  169. *
  170. * @param $accesstoken
  171. * @param $openid
  172. * @return string
  173. */
  174. private function _getInfo($accesstoken, $openid)
  175. {
  176. return 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' . $accesstoken . '&openid=' . $openid;
  177. }
  178. /**
  179. * 验证account_token
  180. *
  181. * @param $accesstoken
  182. * @param $openid
  183. *
  184. * @return string
  185. */
  186. private function _tokenAuth($accesstoken, $openid)
  187. {
  188. return 'https://api.weixin.qq.com/sns/auth?access_token=' . $accesstoken . '&openid=' . $openid;
  189. }
  190. private function _refreshAuth($appid, $refreshtoken)
  191. {
  192. return 'https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=' . $appid . '&grant_type=refresh_token&refresh_token=' . $refreshtoken;
  193. }
  194. /**
  195. * 设置客户端请求地址
  196. *
  197. * @return string
  198. */
  199. private function _setClientRequestUrl()
  200. {
  201. $pattern = '/(&t=([\d]+[^&]*))/';
  202. $t = time();
  203. if (\YunShop::request()->yz_redirect) {
  204. $yz_redirect = base64_decode(\YunShop::request()->yz_redirect);
  205. if (preg_match($pattern, $yz_redirect)) {
  206. $redirect_url = preg_replace($pattern, "&t={$t}", $yz_redirect);
  207. } else {
  208. $redirect_url = $yz_redirect . '&t=' . time();
  209. }
  210. Session::set('client_url', $redirect_url);
  211. } else {
  212. Session::set('client_url', '');
  213. }
  214. }
  215. private function _setClientRequestUrl_v2()
  216. {
  217. $redirect_url = '';
  218. $pattern = '/(&t=([\d]+[^&]*))/';
  219. $t = time();
  220. if (\YunShop::request()->yz_redirect) {
  221. $yz_redirect = base64_decode(\YunShop::request()->yz_redirect);
  222. if (preg_match($pattern, $yz_redirect)) {
  223. $redirect_url = preg_replace($pattern, "&t={$t}", $yz_redirect);
  224. } else {
  225. $redirect_url = $yz_redirect . '&t=' . time();
  226. }
  227. }
  228. return urlencode($redirect_url);
  229. }
  230. /**
  231. * 获取客户端地址
  232. *
  233. * @return mixed
  234. */
  235. private function _getClientRequestUrl()
  236. {
  237. $url = Session::get('client_url') ?: $this->_getFrontJumpUrl();
  238. if ($url === false || $url == '') {
  239. $url = Url::absoluteApp('home') . '&t=' . time();
  240. }
  241. return $url;
  242. }
  243. private function _getFrontJumpUrl()
  244. {
  245. $redirect_url = '';
  246. $pattern = '/(&t=([\d]+[^&]*))/';
  247. $t = time();
  248. if (\YunShop::request()->yz_redirect) {
  249. $yz_redirect = base64_decode(\YunShop::request()->yz_redirect);
  250. if (preg_match($pattern, $yz_redirect)) {
  251. $redirect_url = preg_replace($pattern, "&t={$t}", $yz_redirect);
  252. } else {
  253. $redirect_url = $yz_redirect . '&t=' . time();
  254. }
  255. }
  256. \Log::debug('-----------------front_url----------------', [$redirect_url]);
  257. return $redirect_url;
  258. }
  259. /**
  260. * 公众号开放平台授权登陆
  261. *
  262. * @param $uniacid
  263. * @param $userinfo
  264. * @return array|int|mixed
  265. */
  266. public function unionidLogin($uniacid, $userinfo, $upperMemberId = null)
  267. {
  268. $member_id = parent::unionidLogin($uniacid, $userinfo, $upperMemberId, self::LOGIN_TYPE);
  269. return $member_id;
  270. }
  271. public function updateMemberInfo($member_id, $userinfo)
  272. {
  273. parent::updateMemberInfo($member_id, $userinfo);
  274. \Log::debug('----update_mapping_fans----', $member_id);
  275. $record = array(
  276. 'nickname' => stripslashes($userinfo['nickname']),
  277. 'follow' => $userinfo['subscribe'] ?: 0,
  278. 'tag' => base64_encode(serialize($userinfo))
  279. );
  280. McMappingFansModel::updateData($member_id, $record);
  281. }
  282. public function addMemberInfo($uniacid, $userinfo)
  283. {
  284. $uid = parent::addMemberInfo($uniacid, $userinfo);
  285. \Log::debug('----mapping_fans----', $uid);
  286. //添加mapping_fans表
  287. $this->addFansMember($uid, $uniacid, $userinfo);
  288. return $uid;
  289. }
  290. public function addFansMember($uid, $uniacid, $userinfo)
  291. {
  292. McMappingFansModel::insertData($userinfo, array(
  293. 'uid' => $uid,
  294. 'acid' => $uniacid,
  295. 'uniacid' => $uniacid,
  296. 'salt' => Client::random(8),
  297. ));
  298. }
  299. public function getFansModel($openid)
  300. {
  301. return McMappingFansModel::getFansData($openid);
  302. }
  303. /**
  304. * 会员关联表操作
  305. *
  306. * @param $uniacid
  307. * @param $member_id
  308. * @param $unionid
  309. */
  310. public function addMemberUnionid($uniacid, $member_id, $unionid)
  311. {
  312. MemberUniqueModel::replace(array(
  313. 'uniacid' => $uniacid,
  314. 'unionid' => $unionid,
  315. 'member_id' => $member_id,
  316. 'type' => self::LOGIN_TYPE
  317. ));
  318. }
  319. public function updateFansMember($fan, $member_id, $userinfo)
  320. {
  321. $record = array(
  322. 'uid' => $member_id,
  323. 'nickname' => stripslashes($userinfo['nickname']),
  324. 'follow' => isset($userinfo['subscribe']) ? $userinfo['subscribe'] : 0,
  325. 'tag' => base64_encode(serialize($userinfo))
  326. );
  327. McMappingFansModel::updateDataById($fan->fanid, $record);
  328. }
  329. protected function updateSubMemberInfoV2($uid, $userinfo)
  330. {
  331. SubMemberModel::updateOpenid(
  332. $uid, [
  333. 'yz_openid' => $userinfo['openid'],
  334. 'access_token_1' => $userinfo['access_token'],
  335. 'access_expires_in_1' => time() + $userinfo['expires_in'],
  336. 'refresh_token_1' => $userinfo['refresh_token'],
  337. 'refresh_expires_in_1' => time() + (28 * 24 * 3600)
  338. ]
  339. );
  340. }
  341. public function checkMemberInfo($mcMember, $fansMember, $yzMember)
  342. {
  343. if ($mcMember->uid != $yzMember->member_id) {
  344. $mcMember->uid = $yzMember->member_id;
  345. $mcMember->save();
  346. }
  347. if ($fansMember->uid != $yzMember->member_id) {
  348. $fansMember->uid = $yzMember->member_id;
  349. $fansMember->save();
  350. }
  351. }
  352. /**
  353. * 添加会员主表信息
  354. *
  355. * @param $uniacid
  356. * @param $userinfo
  357. * @return mixed
  358. */
  359. public function addMcMemberInfo($uniacid, $userinfo)
  360. {
  361. $uid = parent::addMemberInfo($uniacid, $userinfo);
  362. return $uid;
  363. }
  364. /**
  365. * 判断是否为手机登录
  366. * @param $uniacid
  367. * @return array
  368. */
  369. public function isPhoneLogin($uniacid)
  370. {
  371. $mid = Member::getMid();
  372. $type = \YunShop::request()->type;
  373. $mobile = \YunShop::request()->mobile;
  374. $password = \YunShop::request()->password;
  375. $yz_redirect = \YunShop::request()->yz_redirect;
  376. if ($mobile && $password) {
  377. $res = (new MemberMobileService)->login();
  378. if ($res['status'] == 1) {
  379. $redirect_url = $this->_getClientRequestUrl();
  380. $res['json']['redirect_url'] = $redirect_url;
  381. }
  382. return $res;
  383. } else {
  384. $this->_setClientRequestUrl();
  385. $redirect_url = Url::absoluteApp('login', ['i' => $uniacid, 'type' => $type, 'mid' => $mid, 'yz_redirect' => $yz_redirect]);
  386. redirect($redirect_url)->send();
  387. }
  388. }
  389. public function chekAccount()
  390. {
  391. $uniacid = \YunShop::app()->uniacid;
  392. $code = \YunShop::request()->code;
  393. $account = AccountWechats::getAccountByUniacid($uniacid);
  394. $appId = $account->key;
  395. $appSecret = $account->secret;
  396. $state = 'yz-' . session_id();
  397. $callback = ($_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  398. $authurl = $this->_getAuthBaseUrl($appId, $callback, $state);
  399. $tokenurl = $this->_getTokenUrl($appId, $appSecret, $code);
  400. if (!empty($code)) {
  401. $redirect_url = $this->_getClientRequestUrl();
  402. $token = \Curl::to($tokenurl)
  403. ->asJsonResponse(true)
  404. ->get();
  405. if (!empty($token) && !empty($token['errmsg']) && $token['errmsg'] == 'invalid code') {
  406. return show_json(5, 'token请求错误');
  407. }
  408. $userinfo = $this->getUserInfo($appId, $appSecret, $token);
  409. if (is_array($userinfo) && !empty($userinfo['errcode'])) {
  410. \Log::debug('微信登陆授权失败-' . $userinfo['errcode']);
  411. return show_json(-3, '微信登陆授权失败');
  412. }
  413. $fans_info = McMappingFansModel::getFansById(\YunShop::app()->getMemberId());
  414. if ($fans_info->openid != $userinfo['openid']) {
  415. \Log::debug('----openid error----', [$fans_info->uid, $userinfo['openid']]);
  416. session_destroy();
  417. Cache::forget($fans_info->uid . ':chekAccount');
  418. redirect($redirect_url)->send();
  419. }
  420. } else {
  421. $this->_setClientRequestUrl();
  422. redirect($authurl)->send();
  423. exit;
  424. }
  425. redirect($redirect_url)->send();
  426. exit;
  427. }
  428. public function checkLogged($login = null)
  429. {
  430. $from = \YunShop::request()->scope;
  431. if (Setting::get('shop.member')['wechat_login_mode'] == '1') {
  432. return (new MemberMobileService)->checkLogged();
  433. }
  434. if (isset($_COOKIE['Yz-Token'])) {
  435. try {
  436. $yz_token = decrypt($_COOKIE['Yz-Token']);
  437. list($token, $expires, $openid, $scope) = explode('\t', $yz_token);
  438. } catch (DecryptException $e) {
  439. setcookie('Yz-Token', '', time() - self::TOKEN_EXPIRE);
  440. return false;
  441. }
  442. if ($scope === 'base' && $from != $scope) {
  443. $login->jump = true;
  444. setcookie('Yz-Token', '', time() - self::TOKEN_EXPIRE);
  445. return false;
  446. }
  447. if (empty($openid)) {
  448. setcookie('Yz-Token', '', time() - self::TOKEN_EXPIRE);
  449. return false;
  450. }
  451. $yz_member = SubMemberModel::getMemberByOpenid($openid);
  452. // 增加token验证
  453. if (is_null($yz_member) || $yz_member->member_id == 0 || $yz_member->access_token_1 != $token) {
  454. setcookie('Yz-Token', '', time() - self::TOKEN_EXPIRE);
  455. return false;
  456. }
  457. if (\YunShop::app()->getMemberId() != $yz_member->member_id) {
  458. Session::set('member_id', $yz_member->member_id);
  459. }
  460. return true;
  461. }
  462. return false;
  463. }
  464. private function wechatScope()
  465. {
  466. if (strpos($this->_getClientRequestUrl(), 'cashier_pay')) {
  467. $set = \Setting::get('plugin.store');
  468. if (isset($set['is_open_warrant']) && 1 == $set['is_open_warrant']) {
  469. return 'snsapi_userinfo';
  470. }
  471. return 'snsapi_base';
  472. }
  473. return 'snsapi_userinfo';
  474. }
  475. }