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 关联后台管理员 ID(sa_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; } /** * 登录(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, ?int $deptId = null): 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'); } $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; } }