Merge branch 'feature/mzl_dev' into develop

# Conflicts:
#	app/common/controller/AuthController.php
#	app/common/logic/AuthLogic.php
#	app/common/model/User.php
#	app/controller/IndexController.php
#	app/functions.php
#	app/middleware/AuthCheck.php
#	app/view/index/qrcode.html
#	composer.json
#	composer.lock
#	config/middleware.php
#	config/route.php
#	config/whitelist.php
This commit is contained in:
mzl
2025-07-11 15:21:00 +08:00
parent 3d8d9f841a
commit a6f2284f51
30 changed files with 1185 additions and 365 deletions
+146 -109
View File
@@ -7,7 +7,6 @@ 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;
@@ -46,87 +45,83 @@ class AuthLogic
return $this->generateTokensAndReturn($user, $system, $data);
}
/**
* 扫码登录
*/
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);
return $this->generateTokensAndReturn($user, $system, $data);
}
/**
* 注册
*/
public function register(array $data)
{
// 获取可用用户编号
$sn = $this->getAvailableUserSn();
$data['sn'] = $sn->user_sn;
try {
// 获取可用用户编号
$sn = $this->getAvailableUserSn();
$data['sn'] = $sn->user_sn;
// 对密码进行哈希处理
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 对密码进行哈希处理
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 获取企业ID
$corpInfo = $this->validateCorp($data['corp_abbr']);
$data['corp_id'] = $corpInfo->id;
// 获取企业ID
$corpInfo = $this->validateCorp($data['corp_abbr']);
$data['corp_id'] = $corpInfo->id;
// 处理账户
$data = $data + normalizeAccount($data['account']);
// 创建用户记录
$user = $this->createUser($data);
// 处理账户
$data = $data + normalizeAccount($data['account']);
// 创建用户记录
$user = $this->createUser($data);
// 更新用户编号的用户 ID
$this->updateUserId($user->id, $sn->user_sn);
// 更新用户编号的用户 ID
$this->updateUserId($user->id, $sn->user_sn);
$system = $this->validateSystem($data['system_code']);
return $this->generateTokensAndReturn($user, $system, $data);
$system = $this->validateSystem($data['system_code']);
return $this->generateTokensAndReturn($user, $system, $data);
} catch (\Throwable $e) {
throw $e;
}
}
/** ----------- 内部方法 ------------- **/
/**
* 查找或创建飞书用户
*/
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;
try {
$phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? '');
$user = User::where('phone', $phone)->where('corp_id', $feishuUserInfo['corp_id'])->first();
if (!$user) {
$user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp);
// 获取编号
$userSn = $this->getAvailableUserSn();
// 创建用户
$user = User::create([
'name' => $feishuUserInfo['en_name'],
'avatar' => $feishuUserInfo['avatar_middle'],
'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''),
'union_id' => $feishuUserInfo['union_id'],
'user_id' => $feishuUserInfo['user_id'],
'open_id' => $feishuUserInfo['open_id'],
'tenant_key' => $feishuUserInfo['tenant_key'],
'corp_id' => $feishuUserInfo['corp_id'],
'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT)
]);
// 更新使用
$this->updateUserId($user->id, $userSn->user_sn);
// 更新编号
if (!$user->update(['sn'=>$userSn->user_sn])) {
throw new BusinessException('编号绑定失败');
}
// 是否登陆
$user->isLogin = false;
}
$userFeishuApp->uid = $user->id;
$userFeishuApp->corp_id = $feishuUserInfo['corp_id'];
if (!$userFeishuApp->save()) {
throw new BusinessException('绑定用户飞书记录失败');
} else {
$user->update([
'union_id' => $feishuUserInfo['union_id'],
'user_id' => $feishuUserInfo['user_id'],
'open_id' => $feishuUserInfo['open_id'],
'tenant_key' => $feishuUserInfo['tenant_key']
]);
}
return $user;
}
} catch (\Throwable $e) {
throw $e;
}
}
/**
@@ -137,38 +132,6 @@ class AuthLogic
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的响应数据
*/
@@ -295,7 +258,8 @@ class AuthLogic
public function feishuAppInfo(string $corpAbbr, string $systemCode)
{
$corpInfo = $this->validateCorp($corpAbbr);
$fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode);
$systemInfo = $this->validateSystem($systemCode);
$fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id);
return [
'app_id' => $fsAppInfo->app_id,
'app_secret' => $fsAppInfo->app_secret,
@@ -303,7 +267,8 @@ class AuthLogic
'scope' => $fsAppInfo->scopes,
'state' => $fsAppInfo->state,
'abbr' => $corpAbbr,
'corp_id' => $corpInfo->id
'corp_id' => $corpInfo->id,
'system_id' => $systemInfo->id
];
}
@@ -349,9 +314,9 @@ class AuthLogic
/**
* 验证飞书应用信息
*/
private function validateFeishuApp(int $corpId, string $systemCode)
private function validateFeishuApp(int $corpId, int $systemId)
{
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first();
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first();
if (!$feishuAppInfo) {
throw new BusinessException('飞书应用不存在', 403);
}
@@ -371,18 +336,6 @@ class AuthLogic
}
}
/**
* 验证扫码令牌
*/
private function validateQrCodeToken(string $qrCodeToken)
{
$userId = Redis::get("qr_code_token:{$qrCodeToken}");
if (!$userId) {
throw new BusinessException('扫码令牌无效或已过期', 401);
}
return $userId;
}
/**
* 获取可用的用户编号
*
@@ -463,4 +416,88 @@ class AuthLogic
Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration'));
return $refreshToken;
}
/**
* 刷新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);
$refreshToken = $this->generateRefreshToken($user->id, $systemCode);
$this->recordUserToken($user->id, $token, $systemCode);
return [
'access_token' => $token,
'refresh_token' => $refreshToken,
'expires_in' => config('jwt.jwt_expiration'),
// 'payload' => $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]);
}
}