validateUser($data['account'], $data['password']); $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, // 方便前端调试,可移除 ]; } /** * 飞书登录 */ public function feishuLogin(array $data): array { $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } /** * 扫码登录 */ public function qrCodeLogin(array $data): array { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); if (!$user || $user->status != 1) { throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } /** * 注册 */ public function register(array $data) { $userSnModel = new UserSn(); $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); if (!$sn) { throw new BusinessException('未找到适用编号'); } $data['sn'] = $sn->user_sn; $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); $user = User::create($data); $this->updateUserId($user->id, $sn->user_sn); return $user; } public function updateUserId($userId, $userSn) { $userSn = User::where('user_sn', $userSn)->first(); if (!$userSn) { throw new BusinessException('用户编号不存在'); } return $userSn->update([ 'user_id' => $userId ]); } /** * 刷新token * @param string $refreshToken * @param string|null $systemCode 可选,指定系统 * @return array */ public function refreshToken(string $refreshToken, ?string $systemCode = null): array { $userId = Cache::get("refresh_token:{$refreshToken}"); if (!$userId) { throw new BusinessException('refresh_token无效或已过期', 401); } $user = User::find($userId); if (!$user || $user->status != 1) { throw new BusinessException('用户无效', 403); } $system = null; if ($systemCode) { $system = Systems::where('code', $systemCode)->first(); if (!$system) { throw new BusinessException('系统不存在', 403); } $this->validateSystemPermission($user, $system); } $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); return [ 'access_token' => $token, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } /** * 记录用户token到 Redis 和 MySQL */ protected function recordUserToken(int $userId, string $token) { $request = request(); $ip = $request ? $request->getRealIp() : ''; $ua = $request ? $request->header('user-agent', '') : ''; $now = time(); $expireAt = $now + config('jwt.jwt_expiration'); // Redis Set 用户token集合 Cache::sAdd("user_tokens:{$userId}", $token); Cache::expire("user_tokens:{$userId}", config('jwt.jwt_expiration')); // Redis Hash token元信息 $meta = [ 'login_ip' => $ip, 'user_agent' => $ua, 'login_time' => $now, 'expired_at' => $expireAt, ]; Cache::hMSet("user_token_meta:{$token}", $meta); Cache::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); // 可选:MySQL 持久化 try { UserTokenLog::create([ 'user_id' => $userId, 'token' => $token, 'login_ip' => $ip, 'user_agent' => $ua, 'login_time' => $now, 'expired_at' => $expireAt, 'is_valid' => 1, ]); } catch (\Throwable $e) { // 写库失败可忽略,但建议日志留存 Log::error('Token log write failed: '.$e->getMessage()); } } /** * 校验token有效性 */ public function verifyToken(string $token): array { $payload = (new Jwt())->verifyToken($token); if (Cache::has("token_blacklist:{$token}")) { throw new BusinessException('token已失效', 403); } return (array)$payload; } // /** // * 登出/失效token // */ // public function logout(?string $token) // { // if ($token) { // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); // } // } // /** // * 强制拉黑token // */ // public function blockToken(string $token) // { // if (!$token) { // throw new BusinessException('token不能为空', 422); // } // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); // } public function logout(?string $token) { if ($token) { Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); // 1. 解析token查用户 $payload = null; try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} $userId = $payload['uid'] ?? null; if ($userId) { Cache::sRem("user_tokens:{$userId}", $token); } // 2. 删除meta Cache::del("user_token_meta:{$token}"); // 3. 更新MySQL为失效 \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); } } public function blockToken(string $token) { if (!$token) { throw new BusinessException('token不能为空', 422); } Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); // 1. 解析token查用户 $payload = null; try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} $userId = $payload['uid'] ?? null; if ($userId) { Cache::sRem("user_tokens:{$userId}", $token); } Cache::del("user_token_meta:{$token}"); \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); } /** * 获取用户所有活跃token及元信息 */ public function getUserTokens(int $userId): array { $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; $result = []; foreach ($tokens as $token) { $meta = Cache::hGetAll("user_token_meta:{$token}"); // 可选再查 MySQL 补充日志记录等 $result[] = [ 'token' => $token, 'meta' => $meta, ]; } return $result; } /** * 强制某用户所有token全部下线 */ public function blockAllTokensOfUser(int $userId) { $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; foreach ($tokens as $token) { $this->blockToken($token); } } /** ----------- 内部方法 ------------- **/ /** * 用户验证 */ private function validateUser(string $account, string $password) { $user = User::byAccount($account)->first(); if (!$user || !password_verify($password, $user->password)) { throw new BusinessException('账号或密码错误', 401); } if ($user->status != 1) { throw new BusinessException('账号已禁用', 403); } return $user; } /** * 系统验证 */ private function validateSystem(string $systemCode) { $system = Systems::where('code', $systemCode)->first(); if (!$system) { throw new BusinessException('系统不存在', 403); } return $system; } /** * 用户系统权限校验 */ private function validateSystemPermission(User $user, Systems $system) { if ($system->is_role) { $systemIds = $user->systems->pluck('id')->toArray(); if (!in_array($system->id, $systemIds)) { throw new BusinessException('无该系统权限', 403); } } } /** * JWT payload 构建 * 可扩展 payload 字段 */ private function buildJwtPayload(User $user, ?Systems $system = null): array { $now = time(); $exp = $now + config('jwt.jwt_expiration'); $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; return [ 'sub' => 'user_' . $user->id, 'aud' => $system ? $system->domain_url : '', 'iat' => $now, 'nbf' => $now, 'exp' => $exp, 'typ' => 'access', 'uid' => $user->id, 'role_ids' => $role_ids, 'system_ids' => $system_ids ]; } /** * JWT签发 */ private function generateAccessToken(array $payload) { return (new Jwt())->generateToken($payload); } /** * 刷新令牌生成 */ private function generateRefreshToken(int $userId) { $refreshToken = bin2hex(random_bytes(32)); Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } /** * 验证扫码令牌 */ private function validateQrCodeToken(string $qrCodeToken) { $userId = Cache::get("qr_code_token:{$qrCodeToken}"); if (!$userId) { throw new BusinessException('扫码令牌无效或已过期', 401); } return $userId; } /** * 调用飞书API获取用户信息(示例) */ private function getFeishuUserInfo(string $code) { // 实际实现需要调用飞书 API return [ 'user_id' => 'feishu_user_123', 'name' => '飞书用户', ]; } /** * 查找或创建飞书用户(示例) */ private function getOrCreateUser(array $feishuUserInfo) { $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); if (!$user) { $user = new User(); $user->feishu_user_id = $feishuUserInfo['user_id']; $user->name = $feishuUserInfo['name']; $user->status = 1; $user->save(); } return $user; } }