[接口]鉴权authToken用户登录login-注册register-退出logout, 并将用户信息保存到redis中

This commit is contained in:
2026-03-04 11:39:11 +08:00
parent ad56d6d4ce
commit 77a898df22
10 changed files with 585 additions and 7 deletions

View File

@@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace app\api\logic;
use app\dice\model\player\DicePlayer;
use app\api\cache\UserCache;
use plugin\saiadmin\exception\ApiException;
use Tinywan\Jwt\JwtToken;
/**
* API 用户登录/注册逻辑(基于 DicePlayer 表)
* 手机号格式限制:+60马来西亚
*/
class UserLogic
{
/** 手机号正则:+60 开头,后跟 910 位数字(马来西亚) */
private const PHONE_REGEX = '/^\+60\d{9,10}$/';
/** 与 DicePlayerLogic 保持一致的密码盐,用于登录校验与注册写入 */
private const PASSWORD_SALT = 'dice_player_salt_2024';
/** 状态:正常 */
private const STATUS_NORMAL = 1;
/**
* 手机号格式校验:+60 开头
*/
public static function validatePhone(string $phone): void
{
if (!preg_match(self::PHONE_REGEX, $phone)) {
throw new ApiException('手机号格式错误,仅支持 +60 开头的马来西亚号码(如 +60123456789');
}
}
/**
* 登录:手机号 + 密码,返回用户信息与 user-token并写入 Redis 缓存
*/
public function login(string $phone, string $password): array
{
self::validatePhone($phone);
$user = DicePlayer::where('phone', $phone)->find();
if (!$user) {
throw new ApiException('手机号未注册');
}
if ((int) $user->status !== self::STATUS_NORMAL) {
throw new ApiException('账号已被禁用');
}
$hashed = $this->hashPassword($password);
if ($user->password !== $hashed) {
throw new ApiException('密码错误');
}
$userArr = $user->hidden(['password'])->toArray();
UserCache::setUser((int) $user->id, $userArr);
$userToken = $this->generateUserToken((int) $user->id);
return [
'user' => $userArr,
'user-token' => $userToken,
'user_id' => (int) $user->id,
];
}
/**
* 注册:手机号 + 密码(+60创建玩家并返回用户信息与 user-token写入 Redis
*/
public function register(string $phone, string $password, ?string $nickname = null): array
{
self::validatePhone($phone);
if (strlen($password) < 6) {
throw new ApiException('密码至少 6 位');
}
$exists = DicePlayer::where('phone', $phone)->find();
if ($exists) {
throw new ApiException('该手机号已注册');
}
$user = new DicePlayer();
$user->phone = $phone;
$user->username = $phone;
if ($nickname !== null && $nickname !== '') {
$user->name = $nickname;
}
// name 未传时由 DicePlayer::onBeforeInsert 默认设为 uid
$user->password = $this->hashPassword($password);
$user->status = self::STATUS_NORMAL;
$user->save();
$userArr = $user->hidden(['password'])->toArray();
UserCache::setUser((int) $user->id, $userArr);
$userToken = $this->generateUserToken((int) $user->id);
return [
'user' => $userArr,
'user-token' => $userToken,
'user_id' => (int) $user->id,
];
}
/**
* 与 DicePlayerLogic 一致的密码加密md5(salt . password)
*/
private function hashPassword(string $password): string
{
return md5(self::PASSWORD_SALT . $password);
}
/**
* 生成 user-tokenJWTplat=api_userid=user_id
*/
private function generateUserToken(int $userId): string
{
$exp = config('api.user_token_exp', 604800);
$result = JwtToken::generateToken([
'id' => $userId,
'plat' => 'api_user',
'access_exp' => $exp,
]);
return $result['access_token'];
}
/**
* 根据 user-token 获取 user_id不写缓存仅解析 JWT
* 若 token 已通过退出接口加入黑名单,返回 null
*/
public static function getUserIdFromToken(string $userToken): ?int
{
if (UserCache::isTokenBlacklisted($userToken)) {
return null;
}
try {
$decoded = JwtToken::verify(1, $userToken);
$extend = $decoded['extend'] ?? [];
if (($extend['plat'] ?? '') !== 'api_user') {
return null;
}
$id = $extend['id'] ?? null;
return $id !== null ? (int) $id : null;
} catch (\Throwable $e) {
return null;
}
}
/**
* 退出登录:将当前 user-token 加入黑名单,使该 token 失效
*/
public static function logout(string $userToken): bool
{
try {
$decoded = JwtToken::verify(1, $userToken);
$exp = (int) ($decoded['exp'] ?? 0);
$ttl = $exp > time() ? $exp - time() : 86400;
return UserCache::addTokenToBlacklist($userToken, $ttl);
} catch (\Throwable $e) {
return false;
}
}
/**
* 从 Redis 获取用户信息key = base64(user_id)),未命中则查 DicePlayer 并回写缓存
*/
public static function getCachedUser(int $userId): array
{
$cached = UserCache::getUser($userId);
if (!empty($cached)) {
return $cached;
}
$user = DicePlayer::find($userId);
if (!$user) {
return [];
}
$arr = $user->hidden(['password'])->toArray();
UserCache::setUser($userId, $arr);
return $arr;
}
}