6b46767d86
- Updated `getProductInfo` method in `Member` model to use a unified model for fetching product data. - Refactored `logLogin` method to utilize `MemberLoginLog` for logging login attempts. - Introduced `getDashboardStats` method in `AuthService` to gather user-specific statistics, including remaining quotas and counts of authorized accounts, published tasks, and works. - Added new database configuration for Douyin business statistics.
253 lines
7.9 KiB
PHP
253 lines
7.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace app\api\service;
|
|
|
|
use app\api\common\Jwt;
|
|
use app\api\model\DyVideoCron;
|
|
use app\api\model\DyVideoUser;
|
|
use app\api\model\DysVideoLog;
|
|
use app\api\model\Member;
|
|
|
|
/**
|
|
* 认证服务
|
|
* 处理用户登录、注册、Token 管理等
|
|
*/
|
|
class AuthService
|
|
{
|
|
/**
|
|
* 用户登录
|
|
* @param string $username 用户名
|
|
* @param string $password 密码
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
public function login(string $username, string $password): array
|
|
{
|
|
// 查找用户
|
|
$member = Member::findByUsername($username);
|
|
if (!$member) {
|
|
throw new \Exception('用户名或密码错误', 4001);
|
|
}
|
|
|
|
// 检查是否被禁用
|
|
if ($member->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();
|
|
}
|
|
}
|