123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- <?php
- namespace App\Services\Client;
- use App\Exceptions\BusinessException;
- use EasyWeChat\OfficialAccount\Application;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Log;
- use Overtrue\Socialite\Contracts\ProviderInterface;
- use Overtrue\Socialite\Contracts\UserInterface;
- class WechatService
- {
- protected Application $app;
- protected AccountService $accountService;
- protected ProviderInterface $socialite;
- public function __construct(AccountService $accountService)
- {
- $this->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
- {
- try {
- // 验证state
- $this->validateState($state);
- // 获取用户信息
- $user = $this->socialite->userFromCode($code);
- // 整理用户信息
- $userInfo = $this->formatUserInfo($user);
- // 添加邀请人信息
- if ($inviteCode) {
- $userInfo['invite_code'] = $inviteCode;
- }
- // 执行登录
- return $this->accountService->wxLogin($user->getId(), $userInfo);
- } catch (BusinessException $e) {
- throw $e;
- } catch (\Exception $e) {
- $this->logError('微信授权回调处理失败', $e, [
- 'code' => $code,
- 'state' => $state,
- 'invite_code' => $inviteCode,
- ]);
- throw new BusinessException('微信授权失败,请稍后重试');
- }
- }
- /**
- * 生成随机state
- */
- protected function generateState(): string
- {
- return md5(uniqid(microtime(true), true));
- }
- /**
- * 缓存授权state
- */
- protected function cacheAuthState(string $state): void
- {
- Cache::put(
- $this->getAuthStateKey($state),
- true,
- config('wechat.auth.cache_ttl')
- );
- }
- /**
- * 验证state
- *
- * @throws BusinessException
- */
- protected function validateState(string $state): void
- {
- if (! Cache::pull($this->getAuthStateKey($state))) {
- throw new BusinessException('无效的授权请求');
- }
- }
- /**
- * 格式化用户信息
- *
- * @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;
- }
- }
|