Files
dafuweng-saiadmin6.x/server/app/api/logic/UserLogic.php

143 lines
4.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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');
}
}
/**
* 与 DicePlayerLogic 一致的密码加密md5(salt . password)
*/
private function hashPassword(string $password): string
{
return md5(self::PASSWORD_SALT . $password);
}
/**
* 登录JSONusername, password, lang, coin, time
* 存在则校验密码并更新 coin累加不存在则创建用户并写入 coin。
* 将会话写入 Redis返回 token 与前端连接地址。
*/
public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time): array
{
$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,
]);
$token = $tokenResult['access_token'];
UserCache::setSessionByUsername($username, $token);
$userArr = $player->hidden(['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight'])->toArray();
UserCache::setUser((int) $player->id, $userArr);
UserCache::setPlayerByUsername($username, $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,
];
}
/**
* 从 JWT 中解析 username仅解码 payload不校验签名与过期用于退出时清除会话
*/
public static function getUsernameFromJwtPayload(string $token): ?string
{
$parts = explode('.', $token);
if (count($parts) !== 3) {
return null;
}
$payload = base64_decode(strtr($parts[1], '-_', '+/'), true);
if ($payload === false) {
return null;
}
$data = json_decode($payload, true);
if (!is_array($data)) {
return null;
}
$extend = $data['extend'] ?? $data;
$username = $extend['username'] ?? null;
return $username !== null ? trim((string) $username) : null;
}
/**
* 从 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;
}
}