app = new Application(config('wechat.official_account.default')); $this->accountService = $accountService; $this->socialite = $this->app->getOAuth(); } /** * 获取微信授权URL * * @param string $redirectUrl 授权后重定向地址 * @param string $scope 授权范围 snsapi_base或snsapi_userinfo * @return array{auth_url: string, state: string} * * @throws BusinessException */ public function getAuthUrl(string $redirectUrl, ?string $scope = null): array { $scope = $scope ?? config('wechat.auth.default_scope'); try { $state = $this->generateState(); $this->cacheAuthState($state); $url = $this->socialite ->scopes([$scope]) ->withState($state) ->redirect($redirectUrl); return [ 'auth_url' => $url, 'state' => $state, ]; } catch (\Exception $e) { $this->logError('生成微信授权URL失败', $e); throw new BusinessException('生成授权链接失败,请稍后重试'); } } /** * 处理微信授权回调 * * @param string $code 授权码 * @param string $state 状态码 * @param string|null $inviteCode 邀请码 * @return array{token: string, user: array} * * @throws BusinessException */ public function handleAuthCallback(string $code, string $state, ?string $inviteCode = null): array { // 验证state $this->validateState($state); // 获取用户信息 $user = $this->socialite->userFromCode($code); // 验证openid $openid = $user->getId(); abort_if(empty($openid), 400, '获取微信openid失败'); // 整理用户信息 $userInfo = $this->formatUserInfo($user); // 添加邀请人信息 if ($inviteCode) { $userInfo['invite_code'] = $inviteCode; } // 执行登录 return $this->accountService->wxLogin($openid, $userInfo); } /** * 缓存授权state */ protected function cacheAuthState(string $state): void { try { $states = Cache::get('wechat_auth_states', []); $states[$state] = true; Cache::put('wechat_auth_states', $states, now()->addMinutes(30)); } catch (\Exception $e) { Log::error('缓存微信授权state失败', [ 'state' => $state, 'error' => $e->getMessage(), ]); throw new BusinessException('系统错误,请稍后重试'); } } /** * 验证state * * @throws BusinessException */ protected function validateState(string $state): void { try { $states = Cache::get('wechat_auth_states', []); if (! isset($states[$state])) { Log::warning('微信授权state无效', ['state' => $state]); throw new BusinessException('授权已过期,请重新授权'); } // 移除已使用的state unset($states[$state]); Cache::put('wechat_auth_states', $states, now()->addMinutes(30)); } catch (BusinessException $e) { throw $e; } catch (\Exception $e) { Log::error('验证微信授权state失败', [ 'state' => $state, 'error' => $e->getMessage(), ]); throw new BusinessException('系统错误,请稍后重试'); } } /** * 生成随机state */ protected function generateState(): string { return md5(uniqid((string) mt_rand(), true)); } /** * 格式化用户信息 * * @return array{ * nickname: string, * avatar: string, * gender: string|null, * country: string|null, * province: string|null, * city: string|null * } */ protected function formatUserInfo(UserInterface $user): array { return [ 'nickname' => $user->getName(), 'avatar' => $user->getAvatar(), 'gender' => $this->formatGender($user->getRaw()['sex'] ?? null), 'country' => $user->getRaw()['country'] ?? null, 'province' => $user->getRaw()['province'] ?? null, 'city' => $user->getRaw()['city'] ?? null, ]; } /** * 格式化性别信息 * * @param int|null $gender 微信返回的性别值:1为男性,2为女性,0为未知 * @return string|null male/female/null */ protected function formatGender(?int $gender): ?string { return match ($gender) { 1 => 'male', 2 => 'female', default => null, }; } /** * 记录错误日志 */ protected function logError(string $message, \Exception $e, array $context = []): void { Log::error($message, array_merge($context, [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ])); } protected function getAuthStateKey(string $state): string { return config('wechat.auth.cache_prefix') . $state; } /** * 获取JSAPI配置 * * @param string $url 当前网页的URL,不包含#及其后面部分 * @return array JSAPI配置信息 * * @throws BusinessException */ public function getJsConfig(string $url): array { $apis = [ 'updateAppMessageShareData', 'updateTimelineShareData', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'getLocation', 'openLocation', 'scanQRCode', ]; return $this->app->getUtils()->buildJsSdkConfig($url, $apis); } }