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, $data['system_code']); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); 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, $data['system_code']); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); 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, $data['system_code']); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } /** * 注册 */ public function register(array $data) { // 获取可用用户编号 $sn = $this->getAvailableUserSn(); $data['sn'] = $sn->user_sn; // 对密码进行哈希处理 $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); // 创建用户记录 $user = $this->createUser($data); // 更新用户编号的用户 ID $this->updateUserId($user->id, $sn->user_sn); // 注册后如果直接登录(返回token) $system = $this->validateSystem($data['system_code']); $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'user' => $user, 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } public function updateUserId($userId, $userSn) { $updated = UserSn::where('user_sn', $userSn) ->update(['user_id' => $userId]); if ($updated === 0) { throw new BusinessException('用户编号不存在或更新失败'); } return $updated; } /** * 获取可用的用户编号 * * @return \app\common\model\UserSn 用户编号模型实例 * @throws BusinessException 当未找到适用编号时抛出 */ private function getAvailableUserSn(): \app\common\model\UserSn { $userSnModel = new UserSn(); $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); if (!$sn) { Log::error('用户注册时未找到适用编号'); throw new BusinessException('未找到适用编号'); } return $sn; } /** * 创建用户记录 * * @param array $data 包含用户信息的数组 * @return \app\common\model\User 用户模型实例 */ private function createUser(array $data): \app\common\model\User { try { return User::create($data); } catch (\Throwable $e) { Log::error('用户注册时创建用户记录失败', [ 'data' => $data, 'error' => $e->getMessage() ]); throw new BusinessException('用户注册失败,请稍后重试', 500); } } /** * 刷新token * @param string $refreshToken * @param string|null $systemCode 可选,指定系统 * @return array */ public function refreshToken(string $refreshToken, ?string $systemCode = null): array { if (!$systemCode) throw new BusinessException('缺少system_code', 422); // 校验refresh_token $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); if (!$userId) { throw new BusinessException('refresh_token无效或已过期', 401); } $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); if ($validToken !== $refreshToken) { throw new BusinessException('refresh_token无效或已过期', 401); } $user = User::find($userId); if (!$user || $user->status != 1) { throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($systemCode); $this->validateSystemPermission($user, $system); $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $this->recordUserToken($user->id, $token, $systemCode); return [ 'access_token' => $token, 'expires_in' => config('jwt.jwt_expiration'), 'payload' => $payload, ]; } /** * 记录用户token到 Redis 和 MySQL */ protected function recordUserToken(int $userId, string $token, string $systemCode) { $request = request(); $ip = $request ? $request->getRealIp() : ''; $ua = $request ? $request->header('user-agent', '') : ''; $now = time(); $expireAt = $now + config('jwt.jwt_expiration'); // Redis Set 用户token集合 Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token); Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration')); // Redis Hash token元信息 $meta = [ 'system_code' => $systemCode, 'login_ip' => $ip, 'user_agent' => $ua, 'login_time' => $now, 'expired_at' => $expireAt, ]; Redis::hMSet("user_token_meta:{$token}", $meta); Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); // 可选:MySQL 持久化 try { UserTokenLog::create([ 'user_id' => $userId, 'token' => $token, 'system_code'=> $systemCode, '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, string $systemCode): array { if (!$systemCode) throw new BusinessException('缺少system_code', 422); $payload = (new Jwt())->verifyToken($token); // 校验黑名单 if (Redis::has("token_blacklist:{$token}")) { throw new BusinessException('token已失效', 403); } // 强制校验aud(系统隔离) if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) { throw new BusinessException('token不属于当前系统', 403); } return (array)$payload; } /** * 登出/失效token */ public function logout(?string $token, ?string $systemCode = null) { if ($token) { Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); $payload = null; try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} $userId = $payload['uid'] ?? null; $aud = $payload['aud'] ?? null; $sc = $systemCode ?? $aud; if ($userId && $sc) { Redis::sRem("user_tokens:{$userId}:{$sc}", $token); } Redis::del("user_token_meta:{$token}"); UserTokenLog::where('token', $token)->update(['is_valid'=>0]); } } /** * 强制拉黑token */ public function blockToken(string $token, ?string $systemCode = null) { if (!$token) { throw new BusinessException('token不能为空', 422); } Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); $payload = null; try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} $userId = $payload['uid'] ?? null; $aud = $payload['aud'] ?? null; $sc = $systemCode ?? $aud; if ($userId && $sc) { Redis::sRem("user_tokens:{$userId}:{$sc}", $token); } Redis::del("user_token_meta:{$token}"); UserTokenLog::where('token', $token)->update(['is_valid'=>0]); } /** * 获取用户所有活跃token及元信息(按system_code) */ public function getUserTokens(int $userId, string $systemCode): array { $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; $result = []; foreach ($tokens as $token) { $meta = Redis::hGetAll("user_token_meta:{$token}"); $result[] = [ 'token' => $token, 'meta' => $meta, ]; } return $result; } /** * 强制某用户所有token全部下线 */ public function blockAllTokensOfUser(int $userId, string $systemCode) { $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; foreach ($tokens as $token) { $this->blockToken($token, $systemCode); } } /** ----------- 内部方法 ------------- **/ /** * 用户验证 */ 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->code, '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, string $systemCode) { $refreshToken = bin2hex(random_bytes(32)); // user_id+system_code只允许一个refresh_token生效 Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration')); // 建立refresh_token到user_id+system_code映射,便于校验 Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } /** * 验证扫码令牌 */ private function validateQrCodeToken(string $qrCodeToken) { $userId = Redis::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; } }