优化登录接口以及中间件
This commit is contained in:
@@ -33,78 +33,6 @@ class UserLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录:手机号 + 密码,返回用户信息与 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);
|
||||
// 同一用户只保留最新一次登录的 token,旧 token 自动失效
|
||||
UserCache::setCurrentUserToken((int) $user->id, $userToken);
|
||||
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);
|
||||
// 同一用户只保留最新一次登录的 token,旧 token 自动失效
|
||||
UserCache::setCurrentUserToken((int) $user->id, $userToken);
|
||||
return [
|
||||
'user' => $userArr,
|
||||
'user-token' => $userToken,
|
||||
'user_id' => (int) $user->id,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 DicePlayerLogic 一致的密码加密:md5(salt . password)
|
||||
*/
|
||||
@@ -114,97 +42,83 @@ class UserLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 user-token(JWT,plat=api_user,id=user_id)
|
||||
* 登录(JSON:username, password, lang, coin, time)
|
||||
* 存在则校验密码并更新 coin(累加);不存在则创建用户并写入 coin。
|
||||
* 将会话写入 Redis,返回 token 与前端连接地址。
|
||||
*/
|
||||
private function generateUserToken(int $userId): string
|
||||
public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time): array
|
||||
{
|
||||
$exp = config('api.user_token_exp', 604800);
|
||||
$result = JwtToken::generateToken([
|
||||
'id' => $userId,
|
||||
'plat' => 'api_user',
|
||||
$username = trim($username);
|
||||
if ($username === '') {
|
||||
throw new ApiException('username 不能为空');
|
||||
}
|
||||
|
||||
$player = DicePlayer::where('username', $username)->find();
|
||||
if ($player) {
|
||||
$hashed = $this->hashPassword($password);
|
||||
if ($player->password !== $hashed) {
|
||||
throw new ApiException('密码错误');
|
||||
}
|
||||
$currentCoin = (float) $player->coin;
|
||||
$player->coin = $currentCoin + $coin;
|
||||
$player->save();
|
||||
} else {
|
||||
$player = new DicePlayer();
|
||||
$player->username = $username;
|
||||
$player->phone = $username;
|
||||
$player->password = $this->hashPassword($password);
|
||||
$player->status = self::STATUS_NORMAL;
|
||||
$player->coin = $coin;
|
||||
$player->save();
|
||||
}
|
||||
|
||||
$exp = (int) config('api.session_expire', 604800);
|
||||
$tokenResult = JwtToken::generateToken([
|
||||
'id' => (int) $player->id,
|
||||
'username' => $username,
|
||||
'plat' => 'api_login',
|
||||
'access_exp' => $exp,
|
||||
]);
|
||||
return $result['access_token'];
|
||||
$token = $tokenResult['access_token'];
|
||||
UserCache::setSessionByUsername($username, $token);
|
||||
|
||||
$userArr = $player->hidden(['password'])->toArray();
|
||||
UserCache::setUser((int) $player->id, $userArr);
|
||||
|
||||
$baseUrl = rtrim(config('api.login_url_base', 'https://127.0.0.1:6777'), '/');
|
||||
$lang = in_array($lang, ['chs', 'en'], true) ? $lang : 'chs';
|
||||
$tokenInUrl = str_replace('%3D', '=', urlencode($token));
|
||||
$url = $baseUrl . '?token=' . $tokenInUrl . '&lang=' . $lang;
|
||||
|
||||
return [
|
||||
'url' => $url,
|
||||
'token' => $token,
|
||||
'lang' => $lang,
|
||||
'user_id' => (int) $player->id,
|
||||
'user' => $userArr,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中解析 user-token(header: user-token 或 Authorization: Bearer)
|
||||
* @param object $request 需有 header(string $name) 方法
|
||||
* 从 JWT 中解析 username(仅解码 payload,不校验签名与过期,用于退出时清除会话)
|
||||
*/
|
||||
public static function getTokenFromRequest(object $request): string
|
||||
public static function getUsernameFromJwtPayload(string $token): ?string
|
||||
{
|
||||
$token = $request->header('user-token') ?? '';
|
||||
if ($token !== '') {
|
||||
return trim((string) $token);
|
||||
}
|
||||
$auth = $request->header('authorization');
|
||||
if ($auth && stripos($auth, 'Bearer ') === 0) {
|
||||
return trim(substr($auth, 7));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求获取当前用户 ID:优先 request->user_id,否则从 header 的 user-token 解析
|
||||
* 中间件未正确注入时仍可兜底解析
|
||||
* @param object $request 需有 user_id 属性及 header() 方法
|
||||
*/
|
||||
public static function getUserIdFromRequest(object $request): ?int
|
||||
{
|
||||
$id = $request->user_id ?? null;
|
||||
if ($id !== null && (int) $id > 0) {
|
||||
return (int) $id;
|
||||
}
|
||||
$token = self::getTokenFromRequest($request);
|
||||
if ($token === '') {
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
return null;
|
||||
}
|
||||
return self::getUserIdFromToken($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 user-token 获取 user_id(不写缓存,仅解析 JWT)
|
||||
* 若 token 已通过退出接口加入黑名单,返回 null
|
||||
*/
|
||||
public static function getUserIdFromToken(string $userToken): ?int
|
||||
{
|
||||
if (UserCache::isTokenBlacklisted($userToken)) {
|
||||
$payload = base64_decode(strtr($parts[1], '-_', '+/'), true);
|
||||
if ($payload === false) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$decoded = JwtToken::verify(1, $userToken);
|
||||
$extend = $decoded['extend'] ?? [];
|
||||
if (($extend['plat'] ?? '') !== 'api_user') {
|
||||
return null;
|
||||
}
|
||||
$id = $extend['id'] ?? null;
|
||||
if ($id === null) {
|
||||
return null;
|
||||
}
|
||||
$userId = (int) $id;
|
||||
// 同一用户只允许当前登记的 token 生效,重新登录/注册后旧 token 失效
|
||||
if (!UserCache::isCurrentUserToken($userId, $userToken)) {
|
||||
return null;
|
||||
}
|
||||
return $userId;
|
||||
} catch (\Throwable $e) {
|
||||
$data = json_decode($payload, true);
|
||||
if (!is_array($data)) {
|
||||
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;
|
||||
}
|
||||
$extend = $data['extend'] ?? $data;
|
||||
$username = $extend['username'] ?? null;
|
||||
return $username !== null ? trim((string) $username) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user