Browse Source

feat:用户端-优化账户所有方法

刘学玺 4 months ago
parent
commit
8eca57d1f0
1 changed files with 247 additions and 122 deletions
  1. 247 122
      app/Services/Client/AccountService.php

+ 247 - 122
app/Services/Client/AccountService.php

@@ -3,14 +3,17 @@
 namespace App\Services\Client;
 
 // 引入所需的类和枚举
-use App\Enums\UserStatus;                   // 用户状态枚举
-use App\Exceptions\BusinessException;        // 业务异常类
-use App\Models\MemberSocialAccount;         // 社交账号模型
-use App\Models\MemberUser;                  // 用户模型
-use App\Services\SmsService;                // 短信服务
-use Illuminate\Support\Facades\Auth;        // 认证门面
-use Illuminate\Support\Facades\Cache;       // 缓存门面
-use Illuminate\Support\Facades\DB;          // 数据库门面
+use App\Enums\UserStatus;
+use App\Exceptions\BusinessException;
+use App\Models\CoachUser;
+use App\Models\MarketDistTeam;
+use App\Models\MemberSocialAccount;
+use App\Models\MemberUser;
+use App\Services\SmsService;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 
 class AccountService
 {
@@ -28,7 +31,6 @@ class AccountService
 
     /**
      * 发送验证码
-     *
      * 业务逻辑:
      * 1. 生成6位随机数字验证码
      * 2. 将验证码保存到缓存中,有效期5分钟
@@ -36,22 +38,20 @@ class AccountService
      * 4. 返回发送成功消息和验证码
      *
      * @param  string  $mobile  手机号
-     * @return array{message: string, code: int} 发送结果
-     *
-     * @throws \Exception 短信发送失败时抛出异常
+     * @return array 包含消息和验证码
      */
     public function sendVerifyCode(string $mobile): array
     {
-        // 生成6位随机验证码
+        // 调用私有方法生成6位随机验证码
         $code = $this->generateVerifyCode();
 
-        // 将验证码保存到缓存中,设置过期时间
+        // 将验证码存入缓存,设置5分钟过期时间
         $this->storeVerifyCode($mobile, $code);
 
-        // 调用短信服务发送验证码
+        // 调用短信服务发送验证码到用户手机
         $this->sendSmsVerifyCode($mobile, $code);
 
-        // 返回发送结果
+        // 返回成功消息和验证码(用于测试环境)
         return [
             'message' => '验证码发送成功',
             'code' => $code,
@@ -60,39 +60,36 @@ class AccountService
 
     /**
      * 用户登录
-     *
      * 业务逻辑:
      * 1. 验证用户输入的验证码是否与缓存中的一致
      * 2. 根据手机号查找用户,不存在则创建新用户
      * 3. 新用户默认状态为开启,记录注册区域
-     * 4. 处理邀请关系(如果有邀请码)
-     * 5. 生成用户认证token
-     * 6. 返回token和用户信息
+     * 4. 生成用户认证token
+     * 5. 返回token和用户信息
      *
      * @param  string  $mobile  手机号
      * @param  string  $code  验证码
-     * @param  string|null  $inviteCode  邀请码 (格式: type_id, 如 user_1, coach_1)
-     * @return array{token: string, user: \App\Models\MemberUser} 登录结果
+     * @return array 包含token和用户信息
      *
      * @throws \Exception 验证码错误时抛出异常
      */
     public function login(string $mobile, string $code, ?string $inviteCode = null): array
     {
-        // 验证用户输入的验证码
+        // 验证用户输入的验证码是否正确
         $this->verifyCode($mobile, $code);
 
-        // 查找或创建用户记录
+        // 根据手机号查找用户,不存在则创建新用户
         $user = $this->findOrCreateUser($mobile);
 
-        // 如果提供了邀请码,处理邀请关系
+        // 如果提供了邀请码,处理用户的邀请关系
         if ($inviteCode) {
             $this->handleInviteRelation($user, $inviteCode);
         }
 
-        // 生成用户认证令牌
+        // 为用户生成新的认证令牌
         $token = $this->createAuthToken($user);
 
-        // 返回登录结果
+        // 返回令牌和用户信息
         return [
             'token' => $token,
             'user' => $user,
@@ -101,62 +98,77 @@ class AccountService
 
     /**
      * 微信登录
-     *
      * 业务逻辑:
      * 1. 根据openid查找或创建社交账号记录
      * 2. 检查社交账号是否已关联用户
      * 3. 未关联则创建新用户并建立关联
-     * 4. 处理邀请关系(如果有)
-     * 5. 生成用户认证token
-     * 6. 返回token和用户信息
+     * 4. 更新用户微信相关信息(昵称、头像、性别)
+     * 5. 处理邀请关系(如果是新用户且有邀请码)
+     * 6. 生成用户认证token
+     * 7. 返回token和用户信息
      *
      * @param  string  $openid  微信openid
-     * @param  array{
-     *     nickname?: string,
-     *     avatar?: string,
-     *     gender?: int,
-     *     invite_code?: string
-     * }  $userInfo  微信用户信息
-     * @return array{token: string, user: \App\Models\MemberUser} 登录结果
+     * @param  array  $userInfo  用户信息,包含 nickname, avatar, gender, invite_code(可选)
+     * @return array 包含token和用户信息
      *
      * @throws BusinessException 业务异常
      */
     public function wxLogin(string $openid, array $userInfo = []): array
     {
-        return DB::transaction(function () {
-            // ... 行内注释保持不变
+        // 使用事务确保数据一致性
+        return DB::transaction(function () use ($openid, $userInfo) {
+            // 查找或创建微信社交账号记录
+            $socialAccount = $this->findOrCreateSocialAccount($openid);
+
+            // 获取关联的用户,如果没有则创建新用户
+            $user = $socialAccount->user ?? $this->createUserFromWechat($userInfo);
+
+            // 如果社交账号未关联用户,建立关联关系
+            if (! $socialAccount->user_id) {
+                $this->linkSocialAccount($socialAccount, $user);
+            }
+
+            // 处理邀请关系(如果提供了邀请码)
+            if (! empty($userInfo['invite_code'])) {
+                $this->handleInviteRelation($user, $userInfo['invite_code']);
+            }
+
+            // 生成用户认证令牌
+            $token = $this->createAuthToken($user);
+
+            // 返回令牌和刷新后的用户信息
+            return [
+                'token' => $token,
+                'user' => $user->fresh(),  // 重新获取用户信息,确保数据最新
+            ];
         });
     }
 
     /**
      * 用户退出
-     *
      * 业务逻辑:
      * 1. 根据用户ID查找用户
-     * 2. 验证用户是否存在
+     * 2. 验证用户是否存在,不存在则中断请求
      * 3. 删除用户所有token
      * 4. 返回退出成功消息
      *
      * @param  int  $userId  用户ID
-     * @return array{message: string} 退出结果
-     *
-     * @throws \Illuminate\Http\Exceptions\HttpResponseException 用户不存在时抛出异常
+     * @return array 退出结果消息
      */
     public function logout(int $userId): array
     {
-        // 查找用户
+        // 查找并验证用户是否存在
         $user = $this->findUser($userId);
 
-        // 删除用户所有token
+        // 删除用户的所有认证令牌
         $this->revokeTokens($user);
 
-        // 返回退出成功消息
+        // 返回退出成功消息
         return ['message' => '退出成功'];
     }
 
     /**
      * 用户注销
-     *
      * 业务逻辑:
      * 1. 获取当前认证用户
      * 2. 验证用户存在且状态为启用
@@ -165,25 +177,25 @@ class AccountService
      * 5. 删除用户所有token
      * 6. 返回注销成功消息
      *
-     * @return array{message: string} 注销结果
+     * @return array 注销结果消息
      *
      * @throws \Exception 用户状态异常时抛出异常
      */
     public function deleteAccount(): array
     {
-        // 获取当前用户
+        // 获取并验证当前登录用户
         $user = $this->getCurrentUser();
 
-        // 确保用户可以被删除
+        // 验证用户是否可以被注销
         $this->ensureUserCanBeDeleted($user);
 
-        // 停用用户账号
+        // 停用用户账号并软删除
         $this->deactivateUser($user);
 
-        // 删除用户令牌
+        // 删除用户的所有认证令牌
         $this->revokeTokens($user);
 
-        // 返回注销成功消息
+        // 返回注销成功消息
         return ['message' => '账号已注销'];
     }
 
@@ -198,38 +210,39 @@ class AccountService
      */
     private function generateVerifyCode(): int
     {
-        // 生成指定长度的随机数字验证码
-        return mt_rand(
-            10 ** (self::VERIFY_CODE_LENGTH - 1),    // 最小值:100000
-            (10 ** self::VERIFY_CODE_LENGTH) - 1     // 最大值:999999
-        );
+        // 计算最小值(例如: 6位数的最小值为100000)
+        $min = 10 ** (self::VERIFY_CODE_LENGTH - 1);
+
+        // 计算最大值(例如: 6位数的最大值为999999)
+        $max = (10 ** self::VERIFY_CODE_LENGTH) - 1;
+
+        // 生成指定范围内的随机数作为验证码
+        return mt_rand($min, $max);
     }
 
     /**
      * 保存验证码到缓存
      *
-     * 业务逻辑:
-     * 1. 使用手机号和前缀生成缓存键
-     * 2. 将验证码保存到缓存
-     * 3. 设置验证码过期时间
+     * 逻辑描述:
+     * 1. 使用手机号作为缓存key
+     * 2. 设置验证码的过期时间
      *
      * @param  string  $mobile  手机号
      * @param  int  $code  验证码
      */
     private function storeVerifyCode(string $mobile, int $code): void
     {
-        // 将验证码保存到缓存,使用手机号作为键名
-        Cache::put(
-            self::VERIFY_CODE_PREFIX.$mobile,    // 缓存键:verify_code:手机号
-            $code,                               // 缓存值:验证码
-            self::VERIFY_CODE_EXPIRE             // 过期时间:5分钟
-        );
+        // 构建缓存键名(verify_code:手机号)
+        $key = self::VERIFY_CODE_PREFIX.$mobile;
+
+        // 将验证码存入缓存,并设置过期时间
+        Cache::put($key, $code, self::VERIFY_CODE_EXPIRE);
     }
 
     /**
      * 发送验证码短信
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 调用短信服务发送验证码
      *
      * @param  string  $mobile  手机号
@@ -239,16 +252,29 @@ class AccountService
      */
     private function sendSmsVerifyCode(string $mobile, int $code): void
     {
-        // 调用短信服务发送验证码
-        $this->smsService->sendVerifyCode($mobile, $code);
+        try {
+            // 调用短信服务发送验证码短信
+            $this->smsService->sendVerifyCode($mobile, $code);
+        } catch (\Exception $e) {
+            // 记录发送失败的日志
+            Log::error('发送验证码失败', [
+                'mobile' => $mobile,
+                'code' => $code,
+                'error' => $e->getMessage(),
+            ]);
+
+            // 重新抛出异常
+            throw $e;
+        }
     }
 
     /**
      * 验证验证码
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 从缓存中获取验证码
-     * 2. 验证码不存在或不匹配则抛出异常
+     * 2. 比对验证码是否匹配
+     * 3. 验证失败则抛出异常
      *
      * @param  string  $mobile  手机号
      * @param  string  $code  验证码
@@ -257,10 +283,10 @@ class AccountService
      */
     private function verifyCode(string $mobile, string $code): void
     {
-        // 从缓存中获取验证码
+        // 使用手机号和前缀构建缓存键,获取存储的验证码
         $cacheCode = Cache::get(self::VERIFY_CODE_PREFIX.$mobile);
 
-        // 比对验证码是否匹配
+        // 验证码不存在或不匹配则抛出业务异常
         if (! $cacheCode || $cacheCode != $code) {
             throw new BusinessException('验证码错误');
         }
@@ -269,21 +295,23 @@ class AccountService
     /**
      * 查找或创建用户
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 根据手机号查找用户
-     * 2. 用户不存在则创建新用户
+     * 2. 不存在则创建新用户
      * 3. 设置用户状态和注册区域
      *
      * @param  string  $mobile  手机号
      */
     private function findOrCreateUser(string $mobile): MemberUser
     {
-        // 根据手机号查找用户
+        // 使用 firstOrCreate 方法查找或创建用户
         return MemberUser::firstOrCreate(
+            // 查询条件:根据手机号查找
             ['mobile' => $mobile],
+            // 创建数据:设置用户状态和注册区域
             [
-                'state' => UserStatus::OPEN->value,
-                'register_area' => request()->header('area_code'),
+                'state' => UserStatus::OPEN->value,  // 设置用户状态为开启
+                'register_area' => request()->header('area_code'),  // 从请求头获取注册区域
             ]
         );
     }
@@ -291,19 +319,20 @@ class AccountService
     /**
      * 查找或创建社交账号
      *
-     * 业务逻辑:
-     * 1. 根据openid查找微信社交账号
-     * 2. 不存在则创建新的社交账号记录
+     * 逻辑描述
+     * 1. 根据openid查找社交账号
+     * 2. 不存在则创建新记录
      *
      * @param  string  $openid  微信openid
      */
     private function findOrCreateSocialAccount(string $openid): MemberSocialAccount
     {
-        // 根据openid查找社交账号
+        // 使用 firstOrCreate 方法查找或创建社交账号记录
         return MemberSocialAccount::firstOrCreate(
+            // 查询条件:平台类型和社交ID
             [
-                'platform' => 'WECHAT',
-                'social_id' => $openid,
+                'platform' => 'WECHAT',     // 设置平台为微信
+                'social_id' => $openid,     // 设置微信openid
             ]
         );
     }
@@ -311,7 +340,7 @@ class AccountService
     /**
      * 创建微信用户
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 创建新用户记录
      * 2. 设置用户基本信息
      * 3. 设置微信相关信息
@@ -320,20 +349,21 @@ class AccountService
      */
     private function createUserFromWechat(array $userInfo): MemberUser
     {
-        // 创建新用户记录
+        // 创建新用户记录
         return MemberUser::create([
-            'state' => UserStatus::OPEN->value,
-            'register_area' => request()->header('area_code'),
-            'nickname' => $userInfo['nickname'] ?? null,
-            'avatar' => $userInfo['avatar'] ?? null,
-            'gender' => $userInfo['gender'] ?? null,
+            'state' => UserStatus::OPEN->value,  // 设置用户状态为开启
+            'register_area' => request()->header('area_code'),  // 从请求头获取注册区域
+            'nickname' => $userInfo['nickname'] ?? null,  // 设置微信昵称,可选
+            'avatar' => $userInfo['avatar'] ?? null,      // 设置微信头像,可选
+            'gender' => $userInfo['gender'] ?? null,      // 设置性别,可选
         ]);
+
     }
 
     /**
      * 关联社交账号和用户
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 更新社交账号的用户ID
      *
      * @param  \App\Models\MemberSocialAccount  $account  社交账号
@@ -341,32 +371,34 @@ class AccountService
      */
     private function linkSocialAccount(MemberSocialAccount $account, MemberUser $user): void
     {
-        // 更新社交账号的用户ID
-        $account->update(['user_id' => $user->id]);
+        // 更新社交账号记录,建立与用户的关联关系
+        $account->update([
+            'user_id' => $user->id,  // 设置关联的用户ID
+        ]);
     }
 
     /**
-     * 生成用户认证令牌
+     * 生成认证令牌
      *
-     * 业务逻辑:
-     * 1. 创建新的认证令牌
-     * 2. 返回令牌字符串
+     * 逻辑描述:
+     * 1. 为用户创建新的认证令牌
      *
      * @param  \App\Models\MemberUser  $user  用户
-     * @return string 认证令牌
+     * @return string 令牌字符串
      */
     private function createAuthToken(MemberUser $user): string
     {
-        // 为用户创建新的认证令牌
-        return $user->createToken('auth-token')->plainTextToken;
+        // 使用 Sanctum 为用户创建新的认证令牌
+        return $user->createToken('auth-token')  // 创建名为 auth-token 的令牌
+            ->plainTextToken;  // 返回令牌的明文字符串
     }
 
     /**
      * 查找用户
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 根据ID查找用户
-     * 2. 用户不存在则抛出404异常
+     * 2. 用户不存在则抛出异常
      *
      * @param  int  $userId  用户ID
      *
@@ -374,17 +406,20 @@ class AccountService
      */
     private function findUser(int $userId): MemberUser
     {
-        // 根据ID查找用户
+        // 根据用户ID查找用户记录
         $user = MemberUser::find($userId);
+
+        // 用户不存在则抛出404异常
         abort_if(! $user, 404, '用户不存在');
 
+        // 返回找到的用户
         return $user;
     }
 
     /**
-     * 获取当前认证用户
+     * 获取当前用户
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 获取当前认证用户
      * 2. 用户未登录则抛出异常
      *
@@ -393,20 +428,24 @@ class AccountService
     private function getCurrentUser(): MemberUser
     {
         /** @var MemberUser $user */
+        // 从认证系统获取当前登录用户
         $user = Auth::user();
+
+        // 用户未登录则抛出业务异常
         if (! $user) {
             throw new BusinessException('用户未登录');
         }
 
+        // 返回当前用户
         return $user;
     }
 
     /**
      * 确保用户可以被删除
      *
-     * 业务逻辑:
+     * 逻辑描述
      * 1. 检查用户状态是否为开启状态
-     * 2. 状态异常则抛出异常
+     * 2. 状态异常则抛出业务异常
      *
      * @param  \App\Models\MemberUser  $user  用户
      *
@@ -414,41 +453,127 @@ class AccountService
      */
     private function ensureUserCanBeDeleted(MemberUser $user): void
     {
-        // 检查用户状态是否正常
+        // 检查用户状态是否为开启状态
         if ($user->state !== UserStatus::OPEN->value) {
+            // 状态异常则抛出业务异常
             throw new BusinessException('用户状态异常');
         }
     }
 
     /**
-     * 停用用户账号
+     * 停用用户
      *
-     * 业务逻辑:
-     * 1. 更新用户状态为关闭
-     * 2. 保存用户状态
-     * 3. 软删除用户记录
+     * 逻辑描述
+     * 1. 更新用户状态为关闭状态
+     * 2. 保存用户状态更新
+     * 3. 软删除用户记录(不会真正删除数据)
      *
      * @param  \App\Models\MemberUser  $user  用户
      */
     private function deactivateUser(MemberUser $user): void
     {
-        // 更新用户状态为关闭
+        // 更新用户状态为关闭状态
         $user->state = UserStatus::CLOSE->value;
+
+        // 保存用户状态更新
         $user->save();
+
+        // 软删除用户记录(不会真正删除数据)
         $user->delete();
     }
 
     /**
      * 撤销用户令牌
      *
-     * 业务逻辑:
-     * 1. 删除用户所有认证令牌
+     * 逻辑描述
+     * 1. 删除用户所有认证令牌记录
      *
      * @param  \App\Models\MemberUser  $user  用户
      */
     private function revokeTokens(MemberUser $user): void
     {
-        // 删除用户所有认证令牌
+        // 删除用户所有认证令牌记录
         $user->tokens()->delete();
     }
+
+    /**
+     * 处理邀请关系
+     *
+     * 逻辑描述:
+     * 1. 解析邀请码格式(格式为: type_id)
+     * 2. 验证邀请码格式是否正确
+     * 3. 分解邀请码获取类型和ID
+     * 4. 根据类型查找邀请人
+     * 5. 邀请人不存在则记录日志并返回
+     * 6. 检查用户是否已在营销团队中
+     * 7. 使用事务创建团队关系
+     *
+     * @param  \App\Models\MemberUser  $user  用户
+     * @param  string  $inviteCode  邀请码
+     */
+    private function handleInviteRelation(MemberUser $user, string $inviteCode): void
+    {
+        try {
+            // 解析邀请码格式(格式为: type_id)
+            $parts = explode('_', $inviteCode);
+
+            // 验证邀请码格式是否正确
+            if (count($parts) !== 2) {
+                // 记录无效邀请码的警告日志
+                Log::warning('Invalid invite code format', ['invite_code' => $inviteCode]);
+
+                return;
+            }
+
+            // 分解邀请码获取类型和ID
+            [$type, $id] = $parts;
+
+            // 根据类型查找邀请人
+            $inviter = match ($type) {
+                'user' => MemberUser::find($id),   // 用户邀请
+                'coach' => CoachUser::find($id),   // 技师邀请
+                default => null
+            };
+
+            // 邀请人不存在则记录日志并返回
+            if (! $inviter) {
+                Log::warning('Inviter not found', [
+                    'type' => $type,
+                    'id' => $id,
+                    'invite_code' => $inviteCode,
+                ]);
+
+                return;
+            }
+
+            // 检查用户是否已在营销团队中
+            $existingTeam = MarketDistTeam::where('user_id', $user->id)->exists();
+            if ($existingTeam) {
+                // 记录用户已在团队中的信息日志
+                Log::info('User already in marketing team', ['user_id' => $user->id]);
+
+                return;
+            }
+
+            // 使用事务创建团队关系
+            DB::transaction(function () use ($user, $inviter) {
+                // 创建营销团队成员记录
+                MarketDistTeam::create([
+                    'user_id' => $user->id,           // 被邀请用户ID
+                    'owner_id' => $inviter->id,       // 邀请人ID
+                    'owner_type' => $inviter::class,  // 邀请人类型(用户/技师)
+                    'level' => 1,                     // 设置团队层级为1
+                    'status' => 1,                    // 设置状态为有效
+                ]);
+            });
+
+        } catch (\Exception $e) {
+            // 记录处理邀请关系时的错误日志
+            Log::error('Failed to handle invite relation', [
+                'user_id' => $user->id,
+                'invite_code' => $inviteCode,
+                'error' => $e->getMessage(),
+            ]);
+        }
+    }
 }