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

247 lines
8.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\app\model\system\SystemUser;
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('Invalid phone format, only +60 Malaysia numbers supported (e.g. +60123456789)');
}
}
/**
* 与 DicePlayerLogic 一致的密码加密md5(salt . password)
*/
private function hashPassword(string $password): string
{
return md5(self::PASSWORD_SALT . $password);
}
/**
* 根据 agent_id 获取同渠道下的所有管理员 ID 列表
* 用于 getGameUrl 接口判断 DicePlayer 是否属于该渠道,同渠道下不重复创建玩家
*
* @param string $agentId 代理标识sa_system_user.agent_id
* @return int[] 管理员 ID 列表,空数组表示未找到或无法解析
*/
public static function getAdminIdsByAgentIdTopDept(string $agentId): array
{
$agentId = trim($agentId);
if ($agentId === '') {
return [];
}
$admin = SystemUser::where('agent_id', $agentId)->find();
if (!$admin) {
return [];
}
$deptId = $admin->dept_id ?? null;
if ($deptId === null || $deptId === '') {
return [(int) $admin->id];
}
$deptId = (int) $deptId;
$adminIds = SystemUser::where('dept_id', $deptId)->column('id');
return array_map('intval', $adminIds ?: [(int) $admin->id]);
}
/**
* 按用户名查找玩家;不存在则创建并绑定渠道/管理员(供 getPlayerInfo 等接口)
*
* @param int|null $adminId 关联后台管理员 IDsa_system_user.id
* @param int|null $deptId 所属渠道 ID
*/
public function findOrCreatePlayerByUsername(string $username, ?int $adminId = null, ?int $deptId = null): DicePlayer
{
$username = trim($username);
if ($username === '') {
throw new ApiException('username is required');
}
$query = DicePlayer::where('username', $username);
if ($deptId !== null && $deptId > 0) {
$query->where('dept_id', $deptId);
}
$player = $query->find();
if ($player) {
if ((int) ($player->status ?? 1) === 0) {
throw new ApiException('Account is disabled');
}
return $player;
}
$player = new DicePlayer();
$player->username = $username;
$player->phone = $username;
$player->password = $this->hashPassword('123456');
$player->status = self::STATUS_NORMAL;
$player->coin = 0;
if ($deptId !== null && $deptId > 0) {
$player->dept_id = $deptId;
}
if ($adminId !== null && $adminId > 0) {
$player->admin_id = $adminId;
if ($deptId === null || $deptId <= 0) {
$adminUser = SystemUser::find($adminId);
if ($adminUser && !empty($adminUser->dept_id)) {
$player->dept_id = $adminUser->dept_id;
}
}
}
$player->save();
return $player;
}
/**
* 登录JSONusername, password, lang, coin, time
* 存在则校验密码并更新 coin累加不存在则创建用户并写入 coin。
* 将会话写入 Redis返回 token 与前端连接地址。
*
* @param int|null $adminId 创建新用户时关联的后台管理员IDsa_system_user.id可选
* @param int[]|null $adminIdsInTopDept 当前管理员顶级部门下的所有管理员ID用于按部门范围查找玩家为空时退化为仅按 username 查找
*/
public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time, ?int $adminId = null, ?array $adminIdsInTopDept = null, ?int $deptId = null, bool $skipPasswordValidation = false): array
{
$username = trim($username);
if ($username === '') {
throw new ApiException('username is required');
}
$query = DicePlayer::where('username', $username);
if ($deptId !== null && $deptId > 0) {
$query->where('dept_id', $deptId);
}
if ($adminIdsInTopDept !== null && !empty($adminIdsInTopDept)) {
$query->whereIn('admin_id', $adminIdsInTopDept);
}
$player = $query->find();
if ($player) {
if ((int) ($player->status ?? 1) === 0) {
throw new ApiException('Account is disabled and cannot log in');
}
if (!$skipPasswordValidation) {
$hashed = $this->hashPassword($password);
if ($player->password !== $hashed) {
throw new ApiException('Wrong password');
}
}
$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;
if ($deptId !== null && $deptId > 0) {
$player->dept_id = $deptId;
}
if ($adminId !== null && $adminId > 0) {
$player->admin_id = $adminId;
$adminUser = SystemUser::find($adminId);
if (($deptId === null || $deptId <= 0) && $adminUser && !empty($adminUser->dept_id)) {
$player->dept_id = $adminUser->dept_id;
}
}
$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);
UserCache::setCurrentUserToken((int) $player->id, $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 = strtolower(trim($lang));
if ($lang !== 'en') {
$lang = 'zh';
}
$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;
}
}