AccountService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <?php
  2. namespace App\Services\Client;
  3. // 引入所需的类和枚举
  4. use App\Enums\UserStatus; // 用户状态枚举
  5. use App\Exceptions\BusinessException; // 业务异常类
  6. use App\Models\MemberSocialAccount; // 社交账号模型
  7. use App\Models\MemberUser; // 用户模型
  8. use App\Services\SmsService; // 短信服务
  9. use Illuminate\Support\Facades\Auth; // 认证门面
  10. use Illuminate\Support\Facades\Cache; // 缓存门面
  11. use Illuminate\Support\Facades\DB; // 数据库门面
  12. class AccountService
  13. {
  14. // 定义验证码相关常量
  15. private const VERIFY_CODE_EXPIRE = 300; // 验证码有效期5分钟
  16. private const VERIFY_CODE_LENGTH = 6; // 验证码长度为6位
  17. private const VERIFY_CODE_PREFIX = 'verify_code:'; // 验证码缓存键前缀
  18. // 构造函数,注入短信服务依赖
  19. public function __construct(
  20. private readonly SmsService $smsService // 使用 readonly 修饰符确保服务实例不可变
  21. ) {}
  22. /**
  23. * 发送验证码
  24. *
  25. * 业务逻辑:
  26. * 1. 生成6位随机数字验证码
  27. * 2. 将验证码保存到缓存中,有效期5分钟
  28. * 3. 调用短信服务发送验证码
  29. * 4. 返回发送成功消息和验证码
  30. *
  31. * @param string $mobile 手机号
  32. * @return array{message: string, code: int} 发送结果
  33. *
  34. * @throws \Exception 短信发送失败时抛出异常
  35. */
  36. public function sendVerifyCode(string $mobile): array
  37. {
  38. // 生成6位随机验证码
  39. $code = $this->generateVerifyCode();
  40. // 将验证码保存到缓存中,设置过期时间
  41. $this->storeVerifyCode($mobile, $code);
  42. // 调用短信服务发送验证码
  43. $this->sendSmsVerifyCode($mobile, $code);
  44. // 返回发送结果
  45. return [
  46. 'message' => '验证码发送成功',
  47. 'code' => $code,
  48. ];
  49. }
  50. /**
  51. * 用户登录
  52. *
  53. * 业务逻辑:
  54. * 1. 验证用户输入的验证码是否与缓存中的一致
  55. * 2. 根据手机号查找用户,不存在则创建新用户
  56. * 3. 新用户默认状态为开启,记录注册区域
  57. * 4. 处理邀请关系(如果有邀请码)
  58. * 5. 生成用户认证token
  59. * 6. 返回token和用户信息
  60. *
  61. * @param string $mobile 手机号
  62. * @param string $code 验证码
  63. * @param string|null $inviteCode 邀请码 (格式: type_id, 如 user_1, coach_1)
  64. * @return array{token: string, user: \App\Models\MemberUser} 登录结果
  65. *
  66. * @throws \Exception 验证码错误时抛出异常
  67. */
  68. public function login(string $mobile, string $code, ?string $inviteCode = null): array
  69. {
  70. // 验证用户输入的验证码
  71. $this->verifyCode($mobile, $code);
  72. // 查找或创建用户记录
  73. $user = $this->findOrCreateUser($mobile);
  74. // 如果提供了邀请码,处理邀请关系
  75. if ($inviteCode) {
  76. $this->handleInviteRelation($user, $inviteCode);
  77. }
  78. // 生成用户认证令牌
  79. $token = $this->createAuthToken($user);
  80. // 返回登录结果
  81. return [
  82. 'token' => $token,
  83. 'user' => $user,
  84. ];
  85. }
  86. /**
  87. * 微信登录
  88. *
  89. * 业务逻辑:
  90. * 1. 根据openid查找或创建社交账号记录
  91. * 2. 检查社交账号是否已关联用户
  92. * 3. 未关联则创建新用户并建立关联
  93. * 4. 处理邀请关系(如果有)
  94. * 5. 生成用户认证token
  95. * 6. 返回token和用户信息
  96. *
  97. * @param string $openid 微信openid
  98. * @param array{
  99. * nickname?: string,
  100. * avatar?: string,
  101. * gender?: int,
  102. * invite_code?: string
  103. * } $userInfo 微信用户信息
  104. * @return array{token: string, user: \App\Models\MemberUser} 登录结果
  105. *
  106. * @throws BusinessException 业务异常
  107. */
  108. public function wxLogin(string $openid, array $userInfo = []): array
  109. {
  110. return DB::transaction(function () {
  111. // ... 行内注释保持不变
  112. });
  113. }
  114. /**
  115. * 用户退出
  116. *
  117. * 业务逻辑:
  118. * 1. 根据用户ID查找用户
  119. * 2. 验证用户是否存在
  120. * 3. 删除用户所有token
  121. * 4. 返回退出成功消息
  122. *
  123. * @param int $userId 用户ID
  124. * @return array{message: string} 退出结果
  125. *
  126. * @throws \Illuminate\Http\Exceptions\HttpResponseException 用户不存在时抛出异常
  127. */
  128. public function logout(int $userId): array
  129. {
  130. // 查找用户
  131. $user = $this->findUser($userId);
  132. // 删除用户所有token
  133. $this->revokeTokens($user);
  134. // 返回退出成功消息
  135. return ['message' => '退出成功'];
  136. }
  137. /**
  138. * 用户注销
  139. *
  140. * 业务逻辑:
  141. * 1. 获取当前认证用户
  142. * 2. 验证用户存在且状态为启用
  143. * 3. 更新用户状态为禁用
  144. * 4. 软删除用户记录
  145. * 5. 删除用户所有token
  146. * 6. 返回注销成功消息
  147. *
  148. * @return array{message: string} 注销结果
  149. *
  150. * @throws \Exception 用户状态异常时抛出异常
  151. */
  152. public function deleteAccount(): array
  153. {
  154. // 获取当前用户
  155. $user = $this->getCurrentUser();
  156. // 确保用户可以被删除
  157. $this->ensureUserCanBeDeleted($user);
  158. // 停用用户账号
  159. $this->deactivateUser($user);
  160. // 删除用户令牌
  161. $this->revokeTokens($user);
  162. // 返回注销成功消息
  163. return ['message' => '账号已注销'];
  164. }
  165. /**
  166. * 生成验证码
  167. *
  168. * 逻辑描述:
  169. * 1. 根据配置的验证码长度生成随机数
  170. * 2. 确保生成的验证码位数固定
  171. *
  172. * @return int 生成的验证码
  173. */
  174. private function generateVerifyCode(): int
  175. {
  176. // 生成指定长度的随机数字验证码
  177. return mt_rand(
  178. 10 ** (self::VERIFY_CODE_LENGTH - 1), // 最小值:100000
  179. (10 ** self::VERIFY_CODE_LENGTH) - 1 // 最大值:999999
  180. );
  181. }
  182. /**
  183. * 保存验证码到缓存
  184. *
  185. * 业务逻辑:
  186. * 1. 使用手机号和前缀生成缓存键
  187. * 2. 将验证码保存到缓存
  188. * 3. 设置验证码过期时间
  189. *
  190. * @param string $mobile 手机号
  191. * @param int $code 验证码
  192. */
  193. private function storeVerifyCode(string $mobile, int $code): void
  194. {
  195. // 将验证码保存到缓存,使用手机号作为键名
  196. Cache::put(
  197. self::VERIFY_CODE_PREFIX.$mobile, // 缓存键:verify_code:手机号
  198. $code, // 缓存值:验证码
  199. self::VERIFY_CODE_EXPIRE // 过期时间:5分钟
  200. );
  201. }
  202. /**
  203. * 发送验证码短信
  204. *
  205. * 业务逻辑:
  206. * 1. 调用短信服务发送验证码
  207. *
  208. * @param string $mobile 手机号
  209. * @param int $code 验证码
  210. *
  211. * @throws \Exception 短信发送失败时抛出异常
  212. */
  213. private function sendSmsVerifyCode(string $mobile, int $code): void
  214. {
  215. // 调用短信服务发送验证码
  216. $this->smsService->sendVerifyCode($mobile, $code);
  217. }
  218. /**
  219. * 验证验证码
  220. *
  221. * 业务逻辑:
  222. * 1. 从缓存中获取验证码
  223. * 2. 验证码不存在或不匹配则抛出异常
  224. *
  225. * @param string $mobile 手机号
  226. * @param string $code 验证码
  227. *
  228. * @throws BusinessException 验证码错误时抛出异常
  229. */
  230. private function verifyCode(string $mobile, string $code): void
  231. {
  232. // 从缓存中获取验证码
  233. $cacheCode = Cache::get(self::VERIFY_CODE_PREFIX.$mobile);
  234. // 比对验证码是否匹配
  235. if (! $cacheCode || $cacheCode != $code) {
  236. throw new BusinessException('验证码错误');
  237. }
  238. }
  239. /**
  240. * 查找或创建用户
  241. *
  242. * 业务逻辑:
  243. * 1. 根据手机号查找用户
  244. * 2. 用户不存在则创建新用户
  245. * 3. 设置用户状态和注册区域
  246. *
  247. * @param string $mobile 手机号
  248. */
  249. private function findOrCreateUser(string $mobile): MemberUser
  250. {
  251. // 根据手机号查找用户
  252. return MemberUser::firstOrCreate(
  253. ['mobile' => $mobile],
  254. [
  255. 'state' => UserStatus::OPEN->value,
  256. 'register_area' => request()->header('area_code'),
  257. ]
  258. );
  259. }
  260. /**
  261. * 查找或创建社交账号
  262. *
  263. * 业务逻辑:
  264. * 1. 根据openid查找微信社交账号
  265. * 2. 不存在则创建新的社交账号记录
  266. *
  267. * @param string $openid 微信openid
  268. */
  269. private function findOrCreateSocialAccount(string $openid): MemberSocialAccount
  270. {
  271. // 根据openid查找社交账号
  272. return MemberSocialAccount::firstOrCreate(
  273. [
  274. 'platform' => 'WECHAT',
  275. 'social_id' => $openid,
  276. ]
  277. );
  278. }
  279. /**
  280. * 创建微信用户
  281. *
  282. * 业务逻辑:
  283. * 1. 创建新用户记录
  284. * 2. 设置用户基本信息
  285. * 3. 设置微信相关信息
  286. *
  287. * @param array $userInfo 微信用户信息
  288. */
  289. private function createUserFromWechat(array $userInfo): MemberUser
  290. {
  291. // 创建新用户记录
  292. return MemberUser::create([
  293. 'state' => UserStatus::OPEN->value,
  294. 'register_area' => request()->header('area_code'),
  295. 'nickname' => $userInfo['nickname'] ?? null,
  296. 'avatar' => $userInfo['avatar'] ?? null,
  297. 'gender' => $userInfo['gender'] ?? null,
  298. ]);
  299. }
  300. /**
  301. * 关联社交账号和用户
  302. *
  303. * 业务逻辑:
  304. * 1. 更新社交账号的用户ID
  305. *
  306. * @param \App\Models\MemberSocialAccount $account 社交账号
  307. * @param \App\Models\MemberUser $user 用户
  308. */
  309. private function linkSocialAccount(MemberSocialAccount $account, MemberUser $user): void
  310. {
  311. // 更新社交账号的用户ID
  312. $account->update(['user_id' => $user->id]);
  313. }
  314. /**
  315. * 生成用户认证令牌
  316. *
  317. * 业务逻辑:
  318. * 1. 创建新的认证令牌
  319. * 2. 返回令牌字符串
  320. *
  321. * @param \App\Models\MemberUser $user 用户
  322. * @return string 认证令牌
  323. */
  324. private function createAuthToken(MemberUser $user): string
  325. {
  326. // 为用户创建新的认证令牌
  327. return $user->createToken('auth-token')->plainTextToken;
  328. }
  329. /**
  330. * 查找用户
  331. *
  332. * 业务逻辑:
  333. * 1. 根据ID查找用户
  334. * 2. 用户不存在则抛出404异常
  335. *
  336. * @param int $userId 用户ID
  337. *
  338. * @throws \Illuminate\Http\Exceptions\HttpResponseException 用户不存在时抛出异常
  339. */
  340. private function findUser(int $userId): MemberUser
  341. {
  342. // 根据ID查找用户
  343. $user = MemberUser::find($userId);
  344. abort_if(! $user, 404, '用户不存在');
  345. return $user;
  346. }
  347. /**
  348. * 获取当前认证用户
  349. *
  350. * 业务逻辑:
  351. * 1. 获取当前认证用户
  352. * 2. 用户未登录则抛出异常
  353. *
  354. * @throws BusinessException 用户未登录时抛出异常
  355. */
  356. private function getCurrentUser(): MemberUser
  357. {
  358. /** @var MemberUser $user */
  359. $user = Auth::user();
  360. if (! $user) {
  361. throw new BusinessException('用户未登录');
  362. }
  363. return $user;
  364. }
  365. /**
  366. * 确保用户可以被删除
  367. *
  368. * 业务逻辑:
  369. * 1. 检查用户状态是否为开启状态
  370. * 2. 状态异常则抛出异常
  371. *
  372. * @param \App\Models\MemberUser $user 用户
  373. *
  374. * @throws BusinessException 用户状态异常时抛出异常
  375. */
  376. private function ensureUserCanBeDeleted(MemberUser $user): void
  377. {
  378. // 检查用户状态是否正常
  379. if ($user->state !== UserStatus::OPEN->value) {
  380. throw new BusinessException('用户状态异常');
  381. }
  382. }
  383. /**
  384. * 停用用户账号
  385. *
  386. * 业务逻辑:
  387. * 1. 更新用户状态为关闭
  388. * 2. 保存用户状态
  389. * 3. 软删除用户记录
  390. *
  391. * @param \App\Models\MemberUser $user 用户
  392. */
  393. private function deactivateUser(MemberUser $user): void
  394. {
  395. // 更新用户状态为关闭
  396. $user->state = UserStatus::CLOSE->value;
  397. $user->save();
  398. $user->delete();
  399. }
  400. /**
  401. * 撤销用户令牌
  402. *
  403. * 业务逻辑:
  404. * 1. 删除用户所有认证令牌
  405. *
  406. * @param \App\Models\MemberUser $user 用户
  407. */
  408. private function revokeTokens(MemberUser $user): void
  409. {
  410. // 删除用户所有认证令牌
  411. $user->tokens()->delete();
  412. }
  413. }