|
|
@@ -0,0 +1,198 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+
|
|
|
+namespace App\Services\Account;
|
|
|
+
|
|
|
+
|
|
|
+use App\Libs\Utils;
|
|
|
+use GuzzleHttp\Client;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+
|
|
|
+class WechatService
|
|
|
+{
|
|
|
+ private $salt = 'bXBfYXVkaW8=';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造微信官方扫码登录地址(跳转后由微信页面自动渲染二维码)
|
|
|
+ *
|
|
|
+ * @param string $state
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ public function buildAuthUrl(string $state): string
|
|
|
+ {
|
|
|
+ $config = (array)config('wechat.web');
|
|
|
+ $query = http_build_query([
|
|
|
+ 'appid' => $config['appid'],
|
|
|
+ 'redirect_uri' => $config['redirect'],
|
|
|
+ 'response_type' => 'code',
|
|
|
+ 'scope' => 'snsapi_login',
|
|
|
+ 'state' => $state,
|
|
|
+ 'connect_redirect' => 1,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return 'https://open.weixin.qq.com/connect/qrconnect?' . $query . '#wechat_redirect';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用授权 code 换取微信用户,并完成登录/注册
|
|
|
+ *
|
|
|
+ * @param string $code
|
|
|
+ * @return array [uid, 是否新注册]
|
|
|
+ */
|
|
|
+ public function loginByCode(string $code): array
|
|
|
+ {
|
|
|
+ $config = (array)config('wechat.web');
|
|
|
+ if (empty($config['secret'])) {
|
|
|
+ Utils::throwError('1002:微信登录未配置AppSecret');
|
|
|
+ }
|
|
|
+
|
|
|
+ $token = $this->request('https://api.weixin.qq.com/sns/oauth2/access_token', [
|
|
|
+ 'appid' => $config['appid'],
|
|
|
+ 'secret' => $config['secret'],
|
|
|
+ 'code' => $code,
|
|
|
+ 'grant_type' => 'authorization_code',
|
|
|
+ ]);
|
|
|
+ if (empty($token['access_token']) || empty($token['openid'])) {
|
|
|
+ dLog('wechat')->info('wechat access_token error', [$token]);
|
|
|
+ Utils::throwError('1002:微信授权失败,请重试');
|
|
|
+ }
|
|
|
+
|
|
|
+ $openid = (string)$token['openid'];
|
|
|
+ $unionid = (string)getProp($token, 'unionid', '');
|
|
|
+
|
|
|
+ $userInfo = $this->request('https://api.weixin.qq.com/sns/userinfo', [
|
|
|
+ 'access_token' => $token['access_token'],
|
|
|
+ 'openid' => $openid,
|
|
|
+ 'lang' => 'zh_CN',
|
|
|
+ ]);
|
|
|
+ if (isset($userInfo['errcode'])) {
|
|
|
+ dLog('wechat')->info('wechat userinfo error', [$userInfo]);
|
|
|
+ Utils::throwError('1002:微信授权失败,请重试');
|
|
|
+ }
|
|
|
+ if ($unionid === '') {
|
|
|
+ $unionid = (string)getProp($userInfo, 'unionid', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->findOrCreateUser(
|
|
|
+ $openid,
|
|
|
+ $unionid,
|
|
|
+ (string)getProp($userInfo, 'nickname', ''),
|
|
|
+ (string)getProp($userInfo, 'headimgurl', '')
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按 unionid/ openid 匹配用户,不存在则注册
|
|
|
+ *
|
|
|
+ * @param string $openid
|
|
|
+ * @param string $unionid
|
|
|
+ * @param string $nickname
|
|
|
+ * @param string $avatar
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function findOrCreateUser(string $openid, string $unionid, string $nickname, string $avatar): array
|
|
|
+ {
|
|
|
+ $user = null;
|
|
|
+ if ($unionid !== '') {
|
|
|
+ $user = DB::table('mp_manage_users')
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->where('wechat_unionid', $unionid)
|
|
|
+ ->first();
|
|
|
+ }
|
|
|
+ if (!$user) {
|
|
|
+ $user = DB::table('mp_manage_users')
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->where('wechat_openid', $openid)
|
|
|
+ ->first();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($user) {
|
|
|
+ $update = ['updated_at' => date('Y-m-d H:i:s')];
|
|
|
+ if ((string)getProp($user, 'avatar') === '' && $avatar !== '') {
|
|
|
+ $update['avatar'] = $avatar;
|
|
|
+ }
|
|
|
+ if ($unionid !== '' && (string)getProp($user, 'wechat_unionid') === '') {
|
|
|
+ $update['wechat_unionid'] = $unionid;
|
|
|
+ }
|
|
|
+ DB::table('mp_manage_users')->where('id', getProp($user, 'id'))->update($update);
|
|
|
+
|
|
|
+ return [(int)getProp($user, 'id'), false];
|
|
|
+ }
|
|
|
+
|
|
|
+ $cpid = (int)config('wechat.cp_id', 4);
|
|
|
+ $account = $this->generateAccount();
|
|
|
+ $nickname = trim($nickname);
|
|
|
+
|
|
|
+ $uid = DB::transaction(function () use ($account, $nickname, $avatar, $cpid, $openid, $unionid) {
|
|
|
+ $randomPwd = makeRandStr(16);
|
|
|
+
|
|
|
+ $uid = DB::table('mp_manage_users')->insertGetId([
|
|
|
+ 'account' => $account,
|
|
|
+ 'nickname' => $nickname,
|
|
|
+ 'avatar' => $avatar,
|
|
|
+ 'o_pass' => '',
|
|
|
+ 'pwd' => md5($randomPwd . $this->salt),
|
|
|
+ 'role' => 'user',
|
|
|
+ 'cpid' => $cpid,
|
|
|
+ 'points' => 0,
|
|
|
+ 'is_enabled' => 1,
|
|
|
+ 'is_deleted' => 0,
|
|
|
+ 'wechat_openid' => $openid,
|
|
|
+ 'wechat_unionid' => $unionid !== '' ? $unionid : null,
|
|
|
+ 'wechat_appid' => (string)config('wechat.web.appid'),
|
|
|
+ 'account_modified'=> 0,
|
|
|
+ 'created_at' => date('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($nickname === '') {
|
|
|
+ DB::table('mp_manage_users')->where('id', $uid)->update([
|
|
|
+ 'nickname' => '微信用户' . $uid,
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $uid;
|
|
|
+ });
|
|
|
+
|
|
|
+ return [(int)$uid, true];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成 wechat_ 前缀的 16 位随机账号
|
|
|
+ *
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ private function generateAccount(): string
|
|
|
+ {
|
|
|
+ for ($i = 0; $i < 5; $i++) {
|
|
|
+ $account = 'wechat_' . makeRandStr(16);
|
|
|
+ if (!DB::table('mp_manage_users')->where('account', $account)->exists()) {
|
|
|
+ return $account;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Utils::throwError('1002:账号生成失败,请重试');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 请求微信接口
|
|
|
+ *
|
|
|
+ * @param string $url
|
|
|
+ * @param array $query
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function request(string $url, array $query): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $client = new Client(['timeout' => 10]);
|
|
|
+ $resp = $client->get($url, ['query' => $query]);
|
|
|
+ $data = json_decode((string)$resp->getBody(), true);
|
|
|
+
|
|
|
+ return is_array($data) ? $data : [];
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ dLog('wechat')->info('wechat request error', [$url, $e->getMessage()]);
|
|
|
+ Utils::throwError('1002:微信授权请求失败,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|