marketDistTeamService = $marketDistTeamService; } /** * 获取当前用户信息 * * 业务逻辑: * 1. 通过 Auth 门面获取当前登录用户信息 * 2. 加载用户的技师信息 * 3. 翻译技师状态 * * @return MemberUser 返回用户信息 * * @throws \Exception 获取用户信息失败时抛出异常 */ public function getUserInfo(): MemberUser { try { /** @var MemberUser $user */ $user = Auth::user(); // 加载技师信息 $user->load('coach:id,user_id,state'); return $user; } catch (\Exception $e) { // 记录错误日志 Log::error('获取用户信息失败', ['error' => $e->getMessage()]); throw $e; } } /** * 用户注册 * * 业务逻辑: * 1. 验证手机号是否已被注册 * 2. 验证短信验证码 * 3. 创建新用户记录 * 4. 处理邀请关系(如果有) * * @param string $mobile 手机号 * @param string $code 验证码 * @param string|null $invite_code 邀请码(选填) * @param int|null $invite_id 邀请人ID(选填) * @param string|null $invite_role 邀请人角色(选填) * @return \Illuminate\Http\JsonResponse 返回注册结果 * * @throws \Exception 注册过程中出现错误时抛出异常 */ public function register(string $mobile, string $code, ?string $invite_code = null, ?int $invite_id = null, ?string $invite_role = null): array { try { DB::beginTransaction(); // 检查手机号是否已注册 abort_if(MemberUser::where('mobile', $mobile)->exists(), 422, '该手机号已注册'); // 验证短信验证码 abort_if(! $this->verifySmsCode($mobile, $code), 422, '验证码错或已过期'); // 创建用户 $user = MemberUser::create([ 'mobile' => $mobile, 'password' => bcrypt(substr($mobile, -6)), // 默认密码为手机号后6位 'nickname' => substr_replace($mobile, '****', 3, 4), // 默认昵称为手机号(中间4位隐藏) ]); // 处理邀请关系 if ($invite_code && $invite_role && $invite_id) { $this->marketDistTeamService->createInviteRelation($user, $invite_code, $invite_id, $invite_role); } DB::commit(); return [ 'user_id' => $user->id, 'mobile' => $mobile, 'invite_code' => $user->invite_code, ]; } catch (\Exception $e) { DB::rollBack(); Log::error('用户注册', ['error' => $e->getMessage(), 'mobile' => $mobile]); throw $e; } } /** * 验证短信验证码 * * 业务逻辑: * 1. 从 Redis 获取存储的验证码 * 2. 比对验证码是否匹配 * 3. 验证成功后删除缓存验证码 * * @param string $mobile 手机号 * @param string $code 待验证的验证码 * @return bool 返回验证结果 */ private function verifySmsCode(string $mobile, string $code): bool { try { $cacheKey = "sms_code:{$mobile}"; $cacheCode = Redis::get($cacheKey); if (! $cacheCode || $cacheCode !== $code) { return false; } Redis::del($cacheKey); // 验证成功后删除验证码 return true; } catch (\Exception $e) { Log::error('验证码验证失败', ['error' => $e->getMessage(), 'mobile' => $mobile]); return false; } } /** * 更新当前用户信息 * * 业务逻辑: * 1. 获取当前登录用户 * 2. 更新用户基本信息 * * @param array $data 待更新的用户数据 * @return \Illuminate\Http\JsonResponse 返回更新结果 * * @throws \Exception 更新失败时抛出异常 */ public function updateUserInfo(array $data): MemberUser { try { DB::beginTransaction(); // 更新用户信息 $user = Auth::user(); $user->update($data); DB::commit(); return $user->fresh(); } catch (\Exception $e) { DB::rollBack(); Log::error('更新用户信息失败', ['error' => $e->getMessage(), 'data' => $data]); throw $e; } } /** * 提交用户反馈 * * 业务逻辑: * 1. 创建用户反馈记录 * 2. 保存反馈内容和图片 * 3. 记录用户联系方式(如果提供) * * @param string $content 反馈内容 * @param array $images 反馈图片数组 * @param string|null $contact 联系方式 * @return \Illuminate\Http\JsonResponse 返回提交结果 * * @throws \Exception 提交反馈失败时抛出异常 */ public function feedback(string $content, array $images = [], ?string $contact = null): void //Feedback { try { DB::beginTransaction(); // 创建反馈记录 // $feedback = Feedback::create([ // 'user_id' => Auth::user()->id, // 'content' => $content, // 'images' => $images, // 'contact' => $contact, // ]); DB::commit(); // return $feedback; } catch (\Exception $e) { DB::rollBack(); Log::error('提交反馈失败', ['error' => $e->getMessage(), 'content' => $content]); throw $e; } } /** * 检查用户是否可以申请成为技师 * * 业务逻辑: * 1. 检查用户是否已经是技师 * 2. 检查是否有正在审核的申请 * 3. 如果不满足条件则抛出异常 * * @throws \Exception 当用户不满足申请条件时抛出异常 */ private function checkCoachApplicationEligibility(): void { /** @var MemberUser $user */ $user = Auth::user(); // 检查是否已经是技师且状态不是已终止 abort_if( $user->coach && $user->coach->state != TechnicianStatus::PENDING->value, 422, '您已是技师,无需重复申请' ); // 检查是否有正在审核的申请 $hasActiveApplication = CoachUser::where('user_id', $user->id) ->whereHas('infoRecords', function ($query) { $query->where('state', TechnicianAuthStatus::AUDITING->value); }) ->exists(); abort_if( $hasActiveApplication, 422, '您有正在审核的申请,请耐心等待' ); } /** * 申请成为技师 * * 业务逻辑: * 1. 检查用户申请资格 * 2. 创建或更新技师基础信息 * 3. 创建申请记录 * 4. 更新关联关系 * * @param CoachApplicationDTO $data 申请数据传输对象 * @return CoachInfoRecord 返回申请记录 * @throws \Exception 申请失败时抛出异常 */ public function applyCoach(CoachApplicationDTO $data): CoachInfoRecord { return DB::transaction(function () use ($data) { // 检查申请资格 $this->checkCoachApplicationEligibility(); // 验证业务规则 $this->validateBusinessRules($data); // 获取当前用户 /** @var MemberUser $user */ $user = Auth::user(); // 创建或获取技师用户记录 $coach = $this->getOrCreateCoachUser($user); // 创建技师信息记录 $infoRecord = $this->createCoachInfoRecord($coach, $data); // 更新技师用户记录的信息记录ID $coach->update(['info_record_id' => $infoRecord->id]); return $infoRecord; }); } /** * 验证技师申请业务规则 * * 验证规则: * 1. 工作年限不能超过实际工作可能年限(年龄-18) * 2. 生活照片数量验证(1-6张) * 3. 手机号格式统一验证 * * @param CoachApplicationDTO $data 申请数据 * @throws \Exception 验证失败时抛出异常 */ private function validateBusinessRules(CoachApplicationDTO $data): void { // 验证工作年限合理性 $maxPossibleWorkYears = $data->age - 18; // 实际可能的最大工作年限 abort_if( $data->workYears > $maxPossibleWorkYears, 422, sprintf('工作年限不能超过%d年(当前年龄%d岁)', $maxPossibleWorkYears, $data->age) ); // 验证生活照片数量 $photoCount = count($data->lifePhotos); abort_if( $photoCount < 1 || $photoCount > 6, 422, '生活照片数量必须在1-6张之间' ); // 验证手机号格式 abort_if( !preg_match('/^1[3-9]\d{9}$/', $data->mobile), 422, '手机号格式不正确' ); } /** * 生成用户邀请码 * * 业务逻辑: * 1. 获取当前户信息 * 2. 根据用户型生成邀请码 * 3. 生成包含邀请参数的接 * 4. 生成邀请维码 * 5. 记录邀请码生成日志 * * @param string $type 邀请码类型(user/coach) * @return array 返回邀请码和二维码信息 * * @throws \Exception 生成邀请码失败时抛出异常 */ public function generateInviteCode(string $type = 'user'): array { try { /** @var MemberUser $user */ // 1. 获取当前用户 $user = Auth::user(); // 2. 根据类型获取邀请人ID和角色 $inviteInfo = $this->getInviteInfo($user, $type); if (! $inviteInfo) { throw new \Exception('无法生邀请码,用户类型不匹配'); } // 3. 生成邀请码 $inviteCode = $this->generateInviteCodeByType( $type, $inviteInfo['id'] ); // 4. 生成邀请链接和二维码 $inviteUrl = $this->generateInviteUrl($inviteCode); $qrCode = $this->generateQrCode($inviteUrl); // 5. 记录日志 Log::info('生成邀请码', [ 'user_id' => $user->id, 'type' => $type, 'invite_id' => $inviteInfo['id'], 'invite_code' => $inviteCode, 'invite_url' => $inviteUrl, ]); return [ 'invite_code' => $inviteCode, 'invite_url' => $inviteUrl, 'qr_code' => $qrCode, ]; } catch (\Exception $e) { Log::error('生成邀请码失', [ 'error' => $e->getMessage(), 'user_id' => Auth::id(), 'type' => $type, ]); throw $e; } } /** * 获取邀请人信息 * * 业务逻辑: * 1. 根据类型判断邀请人身份 * 2. 户类型:直接返回用信息 * 3. 技师类型: * - 验证技师状态 * - 返回技师信息 * * @param \App\Models\MemberUser $user 当前用户 * @param string $type 邀请类型(user/coach) * @return array|null 返回邀请人信息,无效类型返回null */ private function getInviteInfo(MemberUser $user, string $type): ?array { switch ($type) { case 'user': return [ 'id' => $user->id, 'role' => 'user', ]; case 'coach': $coach = $user->coach; if ($coach && $coach->state === TechnicianAuthStatus::PASSED) { return [ 'id' => $coach->id, 'role' => 'coach', ]; } return null; default: return null; } } /** * 根据类型生成邀请码 * * 业务逻辑: * 1. 合类型和ID * 2. 生成格式化的邀请码 * * @param string $type 邀请类型(user/coach) * @param int $id 邀请人ID * @return string 返回格式为 "type_id" 的邀请码 */ private function generateInviteCodeByType(string $type, int $id): string { return sprintf('%s_%d', $type, $id); } /** * 生成邀请链接 * * 业务逻辑: * 1. 获取应用URL * 2. 添加邀请码参数 * 3. 生成完整的邀请链接 * * @param string $inviteCode 邀请码 * @return string 返回完整的邀请链接 */ private function generateInviteUrl(string $inviteCode): string { return config('app.url') . '/invite?' . http_build_query(['invite_code' => $inviteCode]); } /** * 生成二码 * * 业务逻: * 1. 使用 QrCode 库生成 SVG 格式二维码 * 2. 设置二维码大小和边距 * 3. 转换为 base64 编码 * * @param string $content 二维码内容 * @return string 返回 base64 编码的二维码图片 */ private function generateQrCode(string $content): string { $qrImage = QrCode::format('svg') ->size(200) ->margin(2) ->encoding('UTF-8') ->generate($content); return 'data:image/svg+xml;base64,' . base64_encode($qrImage); } /** * 获取技师申请记录 * * 业务逻辑: * 1. 获取当前用户的技师信息 * 2. 如果用户不是技师,返回 null * 3. 获取最新的申请记录 * * @return CoachInfoRecord|null 返回申请记录,未找到时返回 null */ public function getCoachApplication(): ?CoachInfoRecord { try { /** @var MemberUser $user */ $user = Auth::user(); // 确保用户已登录 abort_if(! $user, 401, '用户未登录'); $coach = $user->coach; // 如果用户不是技师,返回 null if (! $coach) { return null; } // 获取最新的申请记录 return $coach->info; } catch (\Exception $e) { Log::error('获取技师申请记录失败', [ 'error' => $e->getMessage(), 'user_id' => Auth::id(), ]); throw $e; } } /** * 创建或获取技师用户记录 * * @param MemberUser $user 用户对象 * @return CoachUser 技师用户记录 */ private function getOrCreateCoachUser(MemberUser $user): CoachUser { // 获取用户技师身份 $coach = $user->coach; // 如果用户不存在技师身份,创建新记录 if (!$coach) { $coach = CoachUser::create([ 'user_id' => $user->id, 'state' => TechnicianStatus::PENDING->value, ]); } return $coach; } /** * 创建技师信息记录 * * @param CoachUser $coach 技师用户对象 * @param CoachApplicationDTO $data 技师信息数据 * @return CoachInfoRecord 技师信息记录 */ private function createCoachInfoRecord(CoachUser $coach, CoachApplicationDTO $data): CoachInfoRecord { // 定义允许的字段和默认值 $allowedFields = [ 'age' => $data->age, 'mobile' => $data->mobile, 'gender' => $data->gender, 'work_years' => $data->workYears, 'intention_city' => $data->intentionCity, 'life_photos' => json_encode($data->lifePhotos), // 将数组转换为JSON字符串 'introduction' => $data->introduction, 'coach_id' => $coach->id, 'state' => TechnicianAuthStatus::AUDITING->value, ]; // 过滤空值,但保留必填字段 $requiredFields = ['age', 'mobile', 'gender', 'work_years', 'intention_city', 'life_photos', 'coach_id', 'state']; $recordData = array_filter( $allowedFields, function ($value, $key) use ($requiredFields) { return !is_null($value) || in_array($key, $requiredFields); }, ARRAY_FILTER_USE_BOTH ); return CoachInfoRecord::create($recordData); } }