isDisabled()) { $member->logLogin(false, 'password'); throw new \Exception('账号已被禁用', 4002); } // 验证密码 if (!$member->verifyPassword($password)) { $member->logLogin(false, 'password'); throw new \Exception('用户名或密码错误', 4001); } // 检查是否过期 if ($member->isExpired()) { $member->logLogin(false, 'password'); throw new \Exception('账号已过期,请联系客服续费', 4003); } // 记录登录日志 $member->logLogin(true, 'password'); // 生成 Token $token = Jwt::encode([ 'userid' => $member->userid, 'username' => $member->username, 'v_type' => $member->v_type, ]); $refreshToken = Jwt::refreshToken($member->userid); // 返回用户信息 return [ 'token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.expire', 604800), 'user' => [ 'userid' => $member->userid, 'username' => $member->username, 'v_type' => $member->v_type, 'endtime' => $member->endtime, 'formtypeid' => $member->formtypeid, ], ]; } /** * 用户注册 * @param string $username 用户名 * @param string $password 密码 * @param string|null $email 邮箱 * @param int|null $formtypeid 代理商ID * @return array * @throws \Exception */ public function register(string $username, string $password, ?string $email = null, ?int $formtypeid = null): array { // 检查用户名是否已存在 $exists = Member::findByUsername($username); if ($exists) { throw new \Exception('用户名已存在', 4004); } // 创建用户 $member = new Member(); $member->username = $username; $member->password = Member::makePassword($password); $member->email = $email; $member->formtypeid = $formtypeid ?? 0; $member->v_type = 0; // 默认套餐 $member->disabled = 0; $member->endtime = 0; $member->regtime = time(); $member->regip = request()->ip(); if (!$member->save()) { throw new \Exception('注册失败,请稍后重试', 5001); } // 自动登录 return $this->login($username, $password); } /** * 刷新 Token * @param string $refreshToken * @return array * @throws \Exception */ public function refreshToken(string $refreshToken): array { $payload = Jwt::decode($refreshToken); if (!$payload || ($payload['type'] ?? '') !== 'refresh') { throw new \Exception('无效的刷新令牌', 4005); } $member = Member::findByUserid($payload['userid']); if (!$member || $member->isDisabled()) { throw new \Exception('用户不存在或已被禁用', 4002); } // 生成新 Token $token = Jwt::encode([ 'userid' => $member->userid, 'username' => $member->username, 'v_type' => $member->v_type, ]); return [ 'token' => $token, 'expires_in' => config('jwt.expire', 604800), ]; } /** * 获取用户信息 * @param int $userid * @return array * @throws \Exception */ public function getUserInfo(int $userid): array { $member = Member::findByUserid($userid); if (!$member) { throw new \Exception('用户不存在', 4006); } // 获取套餐信息 $productInfo = $member->getProductInfo(); $dashboardStats = $this->getDashboardStats($member, $productInfo); return [ 'userid' => $member->userid, 'username' => $member->username, 'v_type' => $member->v_type, 'endtime' => $member->endtime, 'formtypeid' => $member->formtypeid, 'disabled' => $member->disabled, 'video_num' => $member->video_num, 'sp_num' => $member->sp_num, 'product' => $productInfo ? [ 'v_type' => $productInfo['v_type'] ?? null, 'nameu' => $productInfo['nameu'] ?? '', 'video_num' => $productInfo['video_num'] ?? 0, 'account_num' => $productInfo['account_num'] ?? 0, ] : null, 'stats' => $dashboardStats, ]; } /** * 获取首页统计数据。 * * 此处按 acgpmw `dyai/controller/index.php::right()` 的首页语义对齐: * - 账户剩余条数:优先展示用户余额 `member.video_num` * - 授权账号:统计 `dy_video_user` * - 发布任务:统计 `dy_video_cron` * - 发布作品:统计抖音日志分表 `dys_video_log_{userid % 1000}` * * 查询失败时返回 null,避免前端把异常误展示为 0。 * * @param Member $member 当前登录用户 * @param array|null $productInfo 当前套餐信息 * @return array */ private function getDashboardStats(Member $member, ?array $productInfo = null): array { $isUnlimitedQuota = $productInfo && (int) ($productInfo['video_num'] ?? 0) < 0; $stats = [ 'remaining_quota' => $isUnlimitedQuota ? null : (int) ($member->video_num ?? 0), 'remaining_quota_unlimited' => $isUnlimitedQuota, 'published_works_count' => null, 'authorized_account_count' => null, 'published_task_count' => null, ]; try { $stats['authorized_account_count'] = DyVideoUser::countActiveByUserId((int) $member->userid); } catch (\Throwable $exception) { // 统计接口需要尽量稳健,单项查询失败时不阻断登录信息返回。 } try { $stats['published_task_count'] = DyVideoCron::countActiveByUserId((int) $member->userid); } catch (\Throwable $exception) { // 这里与 acgpmw 一样只排除 status=3 的任务,其余状态均计入首页统计。 } try { $stats['published_works_count'] = DysVideoLog::countPublishedByUserId((int) $member->userid); } catch (\Throwable $exception) { // 发布作品日志使用分表存储,查询失败时前端按“待接入”兜底展示。 } return $stats; } /** * 修改密码 * @param int $userid * @param string $oldPassword * @param string $newPassword * @return bool * @throws \Exception */ public function changePassword(int $userid, string $oldPassword, string $newPassword): bool { $member = Member::findByUserid($userid); if (!$member) { throw new \Exception('用户不存在', 4006); } if (!$member->verifyPassword($oldPassword)) { throw new \Exception('原密码错误', 4007); } $member->password = Member::makePassword($newPassword); return $member->save(); } }