diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index c03c7f6..4129ee0 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -9,6 +9,23 @@ use support\Request; class AuthController extends BaseController { + /** + * 不需要头信息的方法 + */ + protected $noNeedHeader = ['feishuAuth']; + + + // 调试 + public function feishuAuth(Request $request) + { + try { + $code = $request->all(); + return $this->success($code); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + public function feishuScope(Request $request) { try { @@ -22,7 +39,7 @@ class AuthController extends BaseController public function login(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_login'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_login'); $result = (new AuthService())->login($data); return $this->success($result, '登录成功'); } catch (\Throwable $e) { @@ -33,11 +50,11 @@ class AuthController extends BaseController public function register(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_register'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_register'); $result = (new AuthService())->register($data); return $this->success($result, '注册成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage().'|'.$e->getFile().'|'.$e->getLine()); + return $this->error($e->getMessage()); } } diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index f399906..0fefb82 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -5,6 +5,7 @@ namespace app\common\controller; use support\exception\BusinessException; use app\common\traits\ResponseTrait; use think\Validate; +use support\Request; abstract class BaseController { @@ -35,4 +36,12 @@ abstract class BaseController // 验证成功,返回原始数据 return $data; } + + /** + * 参数处理 + */ + public function requestData(Request $request) + { + return $request->all() + $request->basic_data; + } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index 0270f17..3c84694 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -7,7 +7,9 @@ use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; use support\Redis; use support\exception\BusinessException; @@ -21,47 +23,27 @@ class AuthLogic public function uPwdLogin(array $data): array { $user = $this->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, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** * 飞书登录 + * 如果用户不存在则新增用户信息 */ public function feishuLogin(array $data): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $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']); + if ($user->isLogin) { + $this->validateSystemPermission($user, $system); + } - // 记录 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, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -71,25 +53,15 @@ class AuthLogic { $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, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -104,29 +76,143 @@ class AuthLogic // 对密码进行哈希处理 $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 处理账户 + $data = $data + normalizeAccount($data['account']); + // 创建用户记录 $user = $this->createUser($data); // 更新用户编号的用户 ID $this->updateUserId($user->id, $sn->user_sn); - // 注册后如果直接登录(返回token) $system = $this->validateSystem($data['system_code']); + return $this->generateTokensAndReturn($user, $system, $data); + } + + /** ----------- 内部方法 ------------- **/ + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { + throw new BusinessException('未找到适用编号'); + } + + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; + + if (!$user->save()) { + throw new BusinessException('用户创建失败'); + } + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; + } + + /** + * 生成并记录token,返回包含token的响应数据 + */ + private function generateTokensAndReturn(User $user, Systems $system, array $data): array + { $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 [ - 'user' => $user, 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, + 'system_code' => $data['system_code'], + 'corp_abbr' => $data['corp_abbr'], + 'login_type' => $data['login_type'] + // 'payload' => $payload, ]; } - public function updateUserId($userId, $userSn) + /** + * 获取飞书用户信息 + */ + private function getFeishuUserInfo(array $data) + { + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + + return $userInfo['data'] + $data + $certificate; + } + + /** + * 更新用户编号 + */ + private function updateUserId($userId, $userSn) { $updated = UserSn::where('user_sn', $userSn) ->update(['user_id' => $userId]); @@ -138,81 +224,6 @@ class AuthLogic 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 */ @@ -256,7 +267,6 @@ class AuthLogic } } - /** * 校验token有效性 */ @@ -280,78 +290,25 @@ class AuthLogic } /** - * 登出/失效token + * 获取飞书应用信息 */ - public function logout(?string $token, ?string $systemCode = null) + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - 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]); - } + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + return [ + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id + ]; } /** - * 强制拉黑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) { @@ -366,7 +323,7 @@ class AuthLogic } /** - * 系统验证 + * 验证系统信息 */ private function validateSystem(string $systemCode) { @@ -378,7 +335,7 @@ class AuthLogic } /** - * 公司验证 + * 验证公司信息 */ private function validateCorp(string $corpAbbr) { @@ -390,7 +347,7 @@ class AuthLogic } /** - * 飞书应用信息 + * 验证飞书应用信息 */ private function validateFeishuApp(int $corpId, string $systemCode) { @@ -402,7 +359,7 @@ class AuthLogic } /** - * 用户系统权限校验 + * 验证用户系统权限 */ private function validateSystemPermission(User $user, Systems $system) { @@ -414,6 +371,54 @@ class AuthLogic } } + /** + * 验证扫码令牌 + */ + private function validateQrCodeToken(string $qrCodeToken) + { + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); + if (!$userId) { + throw new BusinessException('扫码令牌无效或已过期', 401); + } + return $userId; + } + + /** + * 获取可用的用户编号 + * + * @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); + } + } + /** * JWT payload 构建 * 可扩展 payload 字段 @@ -458,70 +463,4 @@ class AuthLogic 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) - { - // // 获取凭证 - // $certificate = certificate($param['state']); - // // 获取token - // $class = new Feishu($certificate['appId'], $certificate['appSecre_s'], $param['state']); - // $userAccessToken = $class->userAccessToken($param['code'], $certificate['rUrl']); - // // 获取用户信息 - // $rst = $class->userInfo($userAccessToken['access_token']); - // if ($rst['code'] != 0) { - // throw new BusinessException('授权失败,请重试'); - // } - // 实际实现需要调用飞书 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; - } - - /** - * 飞书应用信息 - */ - public function feishuAppInfo(string $corpAbbr, string $systemCode) - { - $corpInfo = $this->validateCorp($corpAbbr); - $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); - return [ - 'app_id' => $fsAppInfo->app_id, - 'app_secret' => $fsAppInfo->app_secret, - 'r_url' => $fsAppInfo->r_url, - 'scope' => $fsAppInfo->scopes, - 'state' => $fsAppInfo->state - ]; - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak.php b/app/common/logic/AuthLogic_bak.php index d33cd0e..0e68093 100644 --- a/app/common/logic/AuthLogic_bak.php +++ b/app/common/logic/AuthLogic_bak.php @@ -2,357 +2,592 @@ namespace app\common\logic; +use app\common\model\Corp; +use app\common\model\CorpFeishuApp; +use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; -use support\Cache; +use support\Redis; use support\exception\BusinessException; +use support\Log; class AuthLogic_bak { - /** - * 账号密码登录方法 - * - * 该方法接收包含账号、密码和系统编码的数据数组,验证用户信息和系统信息, - * 检查用户对系统的权限,生成访问令牌和刷新令牌并返回。 - * - * @param array $data 包含登录信息的数组,键名为 'account'(账号)、'password'(密码)、'system_code'(系统编码) - * @return array 包含访问令牌、刷新令牌和令牌过期时间的数组 + * 账号密码登录 */ public function uPwdLogin(array $data): array { - // 验证用户账号和密码,返回用户对象 - $user = $this->validateUser($data['account'], $data['password']); - // 验证系统编码,返回系统对象 - $system = $this->validateSystem($data['system_code']); - // 验证用户对该系统是否有访问权限 - $this->validateSystemPermission($user, $system); - // 准备 JWT 令牌的负载数据 - $payload = $this->preparePayload($user, $system); - // 根据负载数据生成访问令牌 - $token = $this->generateAccessToken($payload); - // 根据用户 ID 生成刷新令牌 - $refreshToken = $this->generateRefreshToken($user->id); + $user = $this->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, ]; } - /** * 飞书登录 - * - * @param array $data - * @return array + * 如果用户不存在则新增用户信息 */ - public function feishuLogin(array $data): array + public function feishuLogin(array $data, bool $isLogin = true): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + if ($user->isLogin === true) { + $this->validateSystemPermission($user, $system); + } + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $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, ]; } /** * 扫码登录 - * - * @param array $data - * @return array */ public function qrCodeLogin(array $data): array { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); + throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $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); + + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 创建用户记录 + $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; + } + + /** + * 获取可用的用户编号 * - * @param string $account - * @param string $password - * @return User|null + * @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 \Exception('账号或密码错误', 401); + throw new BusinessException('账号或密码错误', 401); } - if ($user->status != 1) { - throw new \Exception('账号已禁用', 403); + throw new BusinessException('账号已禁用', 403); } - return $user; } /** - * 验证系统信息 - * - * @param string $systemCode - * @return Systems|null + * 系统验证 */ private function validateSystem(string $systemCode) { $system = Systems::where('code', $systemCode)->first(); if (!$system) { - throw new \Exception('系统不存在', 403); + throw new BusinessException('系统不存在', 403); } - return $system; } /** - * 验证用户系统权限 - * - * @param User $user - * @param Systems $system + * 公司验证 + */ + private function validateCorp(string $corpAbbr) + { + $corp = Corp::where('abbr', $corpAbbr)->first(); + if (!$corp) { + throw new BusinessException('公司不存在', 403); + } + return $corp; + } + + /** + * 飞书应用信息 + */ + private function validateFeishuApp(int $corpId, string $systemCode) + { + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + if (!$feishuAppInfo) { + throw new BusinessException('飞书应用不存在', 403); + } + return $feishuAppInfo; + } + + /** + * 用户系统权限校验 */ 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 \Exception('无该系统权限', 403); + throw new BusinessException('无该系统权限', 403); } } } /** - * 准备 JWT 负载 - * - * @param User $user - * @param Systems $system - * @return array + * JWT payload 构建 + * 可扩展 payload 字段 */ - private function preparePayload(User $user, Systems $system) + private function buildJwtPayload(User $user, ?Systems $system = null): array { - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_'.$user->id, - 'aud' => $system->domain_url, - 'role_ids' => [], - 'system_ids' => [] + $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 ]; - - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - $payload['role_ids'] = $user->roles->pluck('id')->toArray(); - $payload['system_ids'] = $systemIds; - } - - return $payload; } /** - * 生成访问令牌 - * - * @param array $payload - * @return string + * JWT签发 */ private function generateAccessToken(array $payload) { return (new Jwt())->generateToken($payload); } - // private function verifyRefreshToken(string $token) - // { - // return (new Jwt())->verifyToken($token); - // } - /** - * 生成刷新令牌 - * - * @param int $userId - * @return string + * 刷新令牌生成 */ - private function generateRefreshToken(int $userId) + private function generateRefreshToken(int $userId, string $systemCode) { $refreshToken = bin2hex(random_bytes(32)); - Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); + // 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; } - /** - * 调用飞书 API 获取用户信息 - * - * @param string $code - * @return array - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API,这里只是示例 - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 根据飞书用户信息查找或创建本地用户 - * - * @param array $feishuUserInfo - * @return User - */ - 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; - } - /** * 验证扫码令牌 - * - * @param string $qrCodeToken - * @return int */ private function validateQrCodeToken(string $qrCodeToken) { - // 实际实现需要验证扫码令牌,这里只是示例 - $userId = Cache::get("qr_code_token:{$qrCodeToken}"); + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); if (!$userId) { - throw new \Exception('扫码令牌无效或已过期', 401); + throw new BusinessException('扫码令牌无效或已过期', 401); } return $userId; } /** - * 处理用户注册逻辑 - * - * @param array $data - * @return User + * 获取飞书用户信息 */ - public function register(array $data) + private function getFeishuUserInfo(array $data) { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { + // 获取凭证 + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + // 获取token + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + // 获取用户信息 + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + return $userInfo['data'] + $data + $certificate; + } + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { throw new BusinessException('未找到适用编号'); } - $data['sn'] = $sn->user_sn; - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - $user = User::create($data); - return $this->updateUserId($user->id, $sn->user_sn); - } + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; - public function updateUserId($userId, $userSn) - { - $userSn = User::where('user_sn', $userSn)->first(); - if (!$userSn) { - throw new BusinessException('用户编号不存在'); + if (!$user->save()) { + throw new BusinessException('用户创建失败'); } - return $userSn->update([ - 'user_id' => $userId - ]); + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; } + /** - * 校验token + * 飞书应用信息 */ - public function verifyToken(string $token): array + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - $payload = (new Jwt())->verifyToken($token); - if (Cache::has("token_blacklist:{$token}")) { - throw new \Exception('token已失效', 403); - } - return (array)$payload; - } - - /** - * 刷新token - * @param string $refreshToken - * @return array - */ - public function refreshToken(string $refreshToken): array - { - $userId = Cache::get("refresh_token:{$refreshToken}"); - if (!$userId) { - throw new \Exception('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); - } - - // 获取角色ID、系统ID(和你登录时保持一致) - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_' . $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids, - ]; - // 可补充aud、exp等字段,保持和登录生成payload一致 - - $token = (new Jwt())->generateToken($payload); - + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id ]; } - - /** - * 登出,将token加入黑名单 - */ - public function logout(?string $token) - { - if ($token) { - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } - } - - - public function blockToken(string $token) - { - if (!$token) { - throw new \Exception('token不能为空', 422); - } - // 加入黑名单 - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak2.php b/app/common/logic/AuthLogic_bak2.php deleted file mode 100644 index 56f94a7..0000000 --- a/app/common/logic/AuthLogic_bak2.php +++ /dev/null @@ -1,416 +0,0 @@ -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; - } -} \ No newline at end of file diff --git a/app/common/model/User.php b/app/common/model/User.php index ddb7025..23e8b1c 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -58,7 +58,8 @@ class User extends Model 'phone', 'email', 'status', - 'system_code' + 'system_code', + 'corp_id' ]; // 用户所有角色 diff --git a/app/common/model/UserFeishuApp.php b/app/common/model/UserFeishuApp.php new file mode 100644 index 0000000..c64e8cd --- /dev/null +++ b/app/common/model/UserFeishuApp.php @@ -0,0 +1,69 @@ +feishuAppInfo($corpAbbr, $systemCcode); + $logic = new AuthLogic(); + $certificate = $logic->feishuAppInfo($corpAbbr, $systemCcode); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + return ['auth_url' => $feishu->authorize($certificate)]; } /** diff --git a/app/common/service/BasicDataService.php b/app/common/service/BasicDataService.php index af63d7a..e77a4e0 100644 --- a/app/common/service/BasicDataService.php +++ b/app/common/service/BasicDataService.php @@ -9,110 +9,142 @@ use support\exception\BusinessException; class BasicDataService { + // 定义Redis键名前缀常量 + const LOGIN_TYPE_KEY = 'login_types'; + const SYSTEM_CODE_KEY = 'system_codes'; + const CORP_ABBR_KEY = 'corp_abbrs'; + /** * 检查并缓存独立的基础参数 */ public function checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr) { - // 构造缓存键 - $loginTypeKey = 'login_type_' . $loginType; - $systemCodeKey = 'system_code_' . $systemCode; - $corpAbbrKey = 'corp_abbr_' . $corpAbbr; - - // 检查缓存中是否有登录类型参数 - if (!$this->checkParamInCache($loginTypeKey, $loginType)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheLoginType($loginTypeKey, $loginType); + if (!$loginType && !$systemCode && !$corpAbbr) { + throw new BusinessException(htmlpage()); } - // 检查缓存中是否有系统码参数 - if (!$this->checkParamInCache($systemCodeKey, $systemCode)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheSystemCode($systemCodeKey, $systemCode); + // 检查并缓存登录类型 + if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + $this->cacheLoginTypes(); + if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + throw new BusinessException('无效的登录方式'); + } } - // 检查缓存中是否有公司标识参数 - if (!$this->checkParamInCache($corpAbbrKey, $corpAbbr)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheCorpAbbr($corpAbbrKey, $corpAbbr); + // 检查并缓存系统码 + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + $this->cacheSystemCodes(); + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + throw new BusinessException('无效的系统码'); + } + } + + // 检查并缓存公司标识 + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + $this->cacheCorpAbbrs(); + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + throw new BusinessException('无效的公司标识'); + } } return true; } /** - * 检查参数是否在 Redis 缓存中 + * 检查参数是否在Redis集合中 */ private function checkParamInCache($cacheKey, $param) { - return Redis::get($cacheKey) === $param; + return Redis::sIsMember($cacheKey, $param); } /** - * 缓存登录类型 + * 缓存所有登录类型到集合 */ - private function cacheLoginType($cacheKey, $loginType) + private function cacheLoginTypes() { - // 假设我们从数据库或其他来源验证 loginType 的合法性 - if (!$this->validateLoginType($loginType)) { - throw new BusinessException('无效的登录方式'); + $cacheKey = self::LOGIN_TYPE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $loginType); // 1小时有效期 - } - /** - * 缓存系统码 - */ - private function cacheSystemCode($cacheKey, $systemCode) - { - // 假设我们从数据库或其他来源验证 systemCode 的合法性 - if (!$this->validateSystemCode($systemCode)) { - throw new BusinessException('无效的系统码'); + $loginTypes = $this->getAllLoginTypes(); + if (empty($loginTypes)) { + throw new BusinessException('未获取到登录类型数据'); } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $systemCode); // 1小时有效期 + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$loginTypes); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 缓存公司标识 + * 缓存所有系统码到集合 */ - private function cacheCorpAbbr($cacheKey, $corpAbbr) + private function cacheSystemCodes() { - // 假设我们从数据库或其他来源验证 corpAbbr 的合法性 - if (!$this->validateCorpAbbr($corpAbbr)) { - throw new BusinessException('无效的公司标识'); + $cacheKey = self::SYSTEM_CODE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $corpAbbr); // 1小时有效期 + + $systemCodes = $this->getAllSystemCodes(); + if (empty($systemCodes)) { + throw new BusinessException('未获取到系统码数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$systemCodes); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 验证登录方式 + * 缓存所有公司标识到集合 */ - private function validateLoginType($loginType) + private function cacheCorpAbbrs() { - // 假设这里是验证登录方式的具体逻辑 - return in_array($loginType, ['upwd', 'feishu', 'qrcode']); + $cacheKey = self::CORP_ABBR_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; + } + + $corpAbbrs = $this->getAllCorpAbbrs(); + if (empty($corpAbbrs)) { + throw new BusinessException('未获取到公司标识数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$corpAbbrs); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 验证系统码 + * 获取所有有效的登录类型 */ - private function validateSystemCode($systemCode) + private function getAllLoginTypes() { - // 假设这里是验证系统码的具体逻辑 - $system = Systems::where('code', $systemCode)->first(); - return $system !== null; + return ['upwd', 'feishu', 'qrcode']; // 示例数据 } /** - * 验证公司标识 + * 获取所有有效的系统码 */ - private function validateCorpAbbr($corpAbbr) + private function getAllSystemCodes() { - // 假设这里是验证公司标识的具体逻辑 - $company = Corp::where('abbr', $corpAbbr)->first(); - return $company !== null; + return Systems::pluck('code')->toArray(); } -} + + /** + * 获取所有有效的公司标识 + */ + private function getAllCorpAbbrs() + { + return Corp::pluck('abbr')->toArray(); + } +} \ No newline at end of file diff --git a/app/common/traits/ResponseTrait.php b/app/common/traits/ResponseTrait.php index f15dc92..a8adc90 100644 --- a/app/common/traits/ResponseTrait.php +++ b/app/common/traits/ResponseTrait.php @@ -43,13 +43,13 @@ trait ResponseTrait $data = [], array $header = ['Content-Type' => 'application/json'] ): Response { - return $this->result('error', $code, $httpStatus, $message, $data, $header); + return $this->result('fail', $code, $httpStatus, $message, $data, $header); } /** * 统一响应格式方法 * - * @param string $status 响应状态,例如 "success" 或 "error" + * @param string $status 响应状态,例如 "success" 或 "fail" * @param int $code 内部状态码 * @param int $httpStatus HTTP 状态码 * @param string $message 响应消息 diff --git a/app/common/utils/Feishu.php b/app/common/utils/Feishu.php index 5148db1..8428f1c 100644 --- a/app/common/utils/Feishu.php +++ b/app/common/utils/Feishu.php @@ -16,15 +16,15 @@ class Feishu protected $client = null; protected $options = []; protected $error = ''; - protected $gs = 'hesu'; + protected $abbr = 'hesu'; /** * 初始化 */ - public function __construct(string $appId = '', string $appSecret = '', string $gs = 'hesu') + public function __construct(string $appId = '', string $appSecret = '', string $abbr = 'hesu') { - if ($gs === 'mbaf') { + if ($abbr === 'mbaf') { $this->baseUri = 'https://open.larksuite.com/open-apis/'; } else { $this->baseUri = 'https://open.feishu.cn/open-apis/'; @@ -32,7 +32,7 @@ class Feishu $this->appId = $appId; $this->appSecret = $appSecret; - $this->gs = $gs; + $this->abbr = $abbr; $this->client = new Client([ 'base_uri' => $this->baseUri @@ -94,8 +94,8 @@ class Feishu public function authorize($certificate, $url = 'authen/v1/authorize') { $arr = [ - 'app_id' => $certificate['appId'], - 'redirect_uri' => $certificate['rUrl'], + 'app_id' => $certificate['app_id'], + 'redirect_uri' => $certificate['r_url'], 'scope' => $certificate['scope'], 'state' => $certificate['state'] ]; @@ -108,7 +108,7 @@ class Feishu */ public function tenantAccessToken() { - $tenantAccessToken = Cache::get($this->gs.'-tenantAccessToken'); + $tenantAccessToken = Cache::get($this->abbr.'-tenantAccessToken'); if (!$tenantAccessToken) { $this->options["json"] = [ "app_id" => $this->appId, @@ -117,7 +117,7 @@ class Feishu $rst = $this->requestResult('auth/v3/tenant_access_token/internal', 'POST'); if ($rst) { $tenantAccessToken = $rst['tenant_access_token']; - Cache::set($this->gs.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); + Cache::set($this->abbr.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); } else { throw new BusinessException('tenant_access_token 获取失败'); } diff --git a/app/common/validate/UserValidate.php b/app/common/validate/UserValidate.php index f161ba3..7db260d 100644 --- a/app/common/validate/UserValidate.php +++ b/app/common/validate/UserValidate.php @@ -20,6 +20,7 @@ class UserValidate extends Validate 'login_type' => 'require|in:upwd,feishu,qrcode', 'system_code' => 'require|checkSystemCode', 'corp_abbr' => 'require|checkCorpAbbr', + 'feishu_code' => 'require|string' ]; protected $message = [ @@ -44,13 +45,17 @@ class UserValidate extends Validate 'system_code.require' => '系统码不能为空', 'system_code.checkSystemCode' => '系统码不存在或不合法', 'corp_abbr.require' => '公司标识不能为空', - 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法' + 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法', + 'feishu_code.require' => '飞书code不能为空', + 'feishu_code.string' => '飞书code必须是字符串' ]; protected $scene = [ - 'upwd_register' => ['name', 'account', 'password', 'confirm_password', 'login_type', 'system_code'], - 'upwd_login' => ['account', 'password', 'login_type', 'system_code'], - 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'] + 'upwd_register' => ['name', 'account', 'password', 'confirm_password'], + 'feishu_register' => ['feishu_code'], + 'upwd_login' => ['account' => 'require|checkAccount', 'password'], + 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'], + 'feishu_login' => ['feishu_code'] ]; /** diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 2553493..2ea3432 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,79 +6,15 @@ use support\Request; class IndexController { + public function index(Request $request) { - return ' - - - - - Code - - - -
- -
-

🌞 hi~

-

    愿你代码如诗,逻辑清晰,Bug 自动远离!

-

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

-

            加油,永远相信美好的事情即将发生。 ✨

-
- - - - -'; + return htmlpage(); } public function view(Request $request) { - return view('index/view', ['name' => 'webman']); + return view('index/qrcode', ['name' => 'webman']); } public function json(Request $request) @@ -86,4 +22,4 @@ class IndexController return json(['code' => 0, 'msg' => 'ok']); } -} +} \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index 5c9c58d..d20b843 100644 --- a/app/functions.php +++ b/app/functions.php @@ -2,3 +2,41 @@ /** * Here is your custom functions. */ + +/** + * 标准化账号参数 + * + * @param string $account 用户输入的账号(可能是手机号或邮箱) + * @return array 返回 ['phone' => $account] 或 ['email' => $account] + */ +function normalizeAccount(string $account): array +{ + // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 + if (filter_var($account, FILTER_VALIDATE_EMAIL)) { + return ['email' => $account]; + } + + // 清理手机号中的非数字字符 + $cleanPhone = preg_replace('/[^0-9]/', '', $account); + + // 验证手机号格式(简单版:11位数字) + if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { + return ['phone' => $cleanPhone]; + } + + // 如果既不是邮箱也不是手机号,根据业务需求处理 + // 这里默认按手机号处理(可根据实际需求调整) + return ['phone' => $cleanPhone]; +} + +/** + * 公共html页面 + */ +function htmlpage() +{ + static $html; + if (!$html) { + $html = file_get_contents(public_path('mzl.html')); + } + return $html; +} \ No newline at end of file diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php new file mode 100644 index 0000000..3e6eb8a --- /dev/null +++ b/app/middleware/AuthCheck.php @@ -0,0 +1,46 @@ +controller); + $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? []; + + if (in_array($request->action, $noNeedLogin)) { + // 白名单 + return $handler($request); + } else { + try { + $authHeader = $request->header('Authorization'); + if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => '未授权', + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + $token = $matches[1]; + $request->user = (new Jwt())->verifyToken($token); + } catch (\Throwable $e) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => $e->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + return $handler($request); + } + } +} \ No newline at end of file diff --git a/app/middleware/BasicData.php b/app/middleware/BasicData.php new file mode 100644 index 0000000..472456e --- /dev/null +++ b/app/middleware/BasicData.php @@ -0,0 +1,42 @@ +controller); + $noNeedHeader = $controller->getDefaultProperties()['noNeedHeader'] ?? []; + + if (in_array($request->action, $noNeedHeader) || in_array($request->path(), config('whitelist.basi_cdata'))) { + // 白名单 + return $handler($request); + } else { + try { + $corpAbbr = $request->header('Fdesk-Corp-Abbr'); + $systemCode = $request->header('Fdesk-System-Code'); + $loginType = $request->header('Fdesk-Login-Type'); + $basicDataService = new BasicDataService(); + if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { + $request->login_type = $loginType; + $request->system_code = $systemCode; + $request->corp_abbr = $corpAbbr; + $request->basic_data = [ + 'login_type' => $loginType, + 'system_code' => $systemCode, + 'corp_abbr' => $corpAbbr + ]; + } + return $handler($request); + } catch (\Throwable $th) { + throw $th; + } + } + } +} \ No newline at end of file diff --git a/app/middleware/BasicDataMiddleware.php b/app/middleware/BasicDataMiddleware.php deleted file mode 100644 index 6929ef8..0000000 --- a/app/middleware/BasicDataMiddleware.php +++ /dev/null @@ -1,30 +0,0 @@ -header('login_type'); - $systemCode = $request->header('system_code'); - $corpAbbr = $request->header('corp_abbr'); - $basicDataService = new BasicDataService(); - if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { - $request->login_type = $loginType; - $request->system_code = $systemCode; - $request->corp_abbr = $corpAbbr; - } - return $next($request); - } catch (\Throwable $th) { - throw $th; - } - } - -} diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html new file mode 100644 index 0000000..26d1918 --- /dev/null +++ b/app/view/index/qrcode.html @@ -0,0 +1,46 @@ + + + + + 飞书扫码登录示例 + + + + + +
+ + + + diff --git a/config/middleware.php b/config/middleware.php index 5512630..cf0335a 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -12,8 +12,10 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + return [ '' => [ - \app\middleware\BasicDataMiddleware::class + \app\middleware\AccessControl::class, + \app\middleware\BasicData::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index aaf9098..3b642cf 100644 --- a/config/route.php +++ b/config/route.php @@ -28,8 +28,13 @@ use Webman\Route; Route::group('/common', function () { Route::group('/auth', function () { - Route::post('/login', [app\common\controller\AuthController::class, 'login']); - Route::post('/register', [app\common\controller\AuthController::class, 'register']); - Route::post('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + Route::any('/login', [app\common\controller\AuthController::class, 'login']); + Route::any('/register', [app\common\controller\AuthController::class, 'register']); + Route::any('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + + + + // 调试 + Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); }); \ No newline at end of file diff --git a/config/whitelist.php b/config/whitelist.php new file mode 100644 index 0000000..8071847 --- /dev/null +++ b/config/whitelist.php @@ -0,0 +1,9 @@ + [ + '/', + '/index/index', + // '/index/view' + ] +]; \ No newline at end of file diff --git a/public/mzl.html b/public/mzl.html new file mode 100644 index 0000000..9eb4e4b --- /dev/null +++ b/public/mzl.html @@ -0,0 +1,64 @@ + + + + + Code + + + +
+ +
+

🌞 hi~

+

    愿你代码如诗,逻辑清晰,Bug 自动远离!

+

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

+

            加油,永远相信美好的事情即将发生。 ✨

+
+ + + + \ No newline at end of file