0 && !isset($visited[$currentId])) { $visited[$currentId] = true; $dept = SystemDept::find($currentId); if (!$dept) { return null; } $parentId = (int) ($dept->parent_id ?? 0); if ($parentId === 0) { return $currentId; } $currentId = $parentId; } return $currentId > 0 ? $currentId : null; } /** * 根据顶级部门 id,递归获取其下所有部门 id(含自身),仅用 id 和 parent_id */ private static function getAllDeptIdsUnderTop(int $topId): array { $deptIds = [$topId]; $prevCount = 0; while (count($deptIds) > $prevCount) { $prevCount = count($deptIds); $children = SystemDept::whereIn('parent_id', $deptIds)->column('id'); $deptIds = array_unique(array_merge($deptIds, array_map('intval', $children))); } return array_values($deptIds); } /** * 根据 agent_id 获取当前管理员所在顶级部门下的所有管理员 ID 列表 * 使用 SystemDept 的 id 和 parent_id 字段遍历:先向上找顶级部门(parent_id=0),再向下收集所有子部门 * 用于 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; $topId = self::getTopDeptIdByParentId($deptId); if ($topId === null) { return [(int) $admin->id]; } $deptIds = self::getAllDeptIdsUnderTop($topId); if (empty($deptIds)) { $deptIds = [$deptId]; } $adminIds = SystemUser::whereIn('dept_id', $deptIds)->column('id'); return array_map('intval', $adminIds); } /** * 登录(JSON:username, password, lang, coin, time) * 存在则校验密码并更新 coin(累加);不存在则创建用户并写入 coin。 * 将会话写入 Redis,返回 token 与前端连接地址。 * * @param int|null $adminId 创建新用户时关联的后台管理员ID(sa_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): array { $username = trim($username); if ($username === '') { throw new ApiException('username is required'); } $query = DicePlayer::where('username', $username); 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'); } $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 ($adminId !== null && $adminId > 0) { $player->admin_id = $adminId; } $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; } }