新增权限功能

This commit is contained in:
mzl
2025-07-11 14:52:16 +08:00
parent 3a72f8ec0a
commit 92eff91747
18 changed files with 463 additions and 244 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]);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace app\common\logic;
use Illuminate\Database\Eloquent\Model;
use support\exception\BusinessException;
class BaseLogic
{
/**
* 获取列表
*/
public function getList(array $params, string $modelClass)
{
$query = $modelClass::query();
// 你可以在此处添加分页、排序等功能
if (isset($params['page']) && isset($params['per_page'])) {
$query->paginate($params['per_page'], ['*'], 'page', $params['page']);
}
return $query->get();
}
/**
* 获取单个记录
*/
public function getDetail(int $id, string $modelClass)
{
$record = $modelClass::find($id);
if (!$record) {
throw new BusinessException("记录不存在", 404);
}
return $record;
}
/**
* 创建记录
*/
public function create(array $data, string $modelClass)
{
$model = new $modelClass;
$model->fill($data); // 根据字段填充数据
try {
$model->save();
} catch (\Throwable $e) {
throw new BusinessException("创建记录失败: " . $e->getMessage(), 500);
}
return $model;
}
/**
* 更新记录
*/
public function update(array $data, string $modelClass)
{
$record = $modelClass::find($data['id']);
if (!$record) {
throw new BusinessException("记录不存在", 404);
}
$record->update($data);
return $record;
}
/**
* 删除记录
*/
public function delete(int $id, string $modelClass)
{
$record = $modelClass::find($id);
if (!$record) {
throw new BusinessException("记录不存在", 404);
}
try {
$record->delete();
} catch (\Throwable $e) {
throw new BusinessException("删除记录失败: " . $e->getMessage(), 500);
}
return true;
}
}
+78 -10
View File
@@ -27,16 +27,82 @@ class PermLogic
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public function buildTree($resources, int $parentId = 0): array
// public function buildTree($resources, int $parentId = 0): array
// {
// $tree = [];
// foreach ($resources as $node) {
// if ($node->parent_id !== $parentId) continue;
// // 子节点
// $children = [];
// if ($node->type === 'directory') {
// $children = $this->buildTree($resources, $node->id);
// }
// // 本级权限列表
// $authList = $resources
// ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
// ->map(fn($r) => [
// 'id' => $r->id,
// 'title' => $r->name,
// 'auth_mark' => $r->code,
// ])->values()->all();
// $tree[] = [
// 'id' => $node->id,
// 'name' => $node->name,
// 'path' => $node->url,
// 'component' => $node->type === 'directory' ? 'Layout' : 'Page',
// 'meta' => [
// 'title' => $node->name,
// 'icon' => $node->icon,
// 'keepAlive' => true,
// 'authList' => $authList,
// 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
// ],
// 'children' => $children,
// ];
// }
// return $tree;
// }
/**
* 构建目录/菜单树,节点包含权限与打开方式,并拼接完整路径到 component 字段
* @param \Illuminate\Support\Collection $resources
* @param int $parentId
* @param string $parentPath
* @return array
*/
public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array
{
$tree = [];
foreach ($resources as $node) {
if ($node->parent_id !== $parentId) continue;
// 子节点
$children = $this->buildTree($resources, $node->id);
// 拼接完整路径
$currentPath = $parentPath;
// 去掉结尾斜杠,防止重复
if ($currentPath && substr($currentPath, -1) === '/') {
$currentPath = rtrim($currentPath, '/');
}
// 子菜单的 path 不要补全斜杠
if (!empty($node->url)) {
// 如果当前是根节点或父路径为空,直接用自身
if (!$currentPath) {
$currentPath = $node->url;
} else {
// 防止 url 以 / 开头导致双斜杠
$url = ltrim($node->url, '/');
$currentPath = $currentPath . '/' . $url;
}
}
// 本级权限列表
// 子节点
$children = [];
if ($node->type === 'directory') {
$children = $this->buildTree($resources, $node->id, $currentPath);
}
// 权限
$authList = $resources
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
->map(fn($r) => [
@@ -46,22 +112,24 @@ class PermLogic
])->values()->all();
$tree[] = [
'id' => $node->id,
'name' => $node->name,
'path' => $node->url,
'meta' => [
'id' => $node->id,
'name' => $node->name,
'path' => $node->url,
'component' => $currentPath, // 这里就是全路径
'meta' => [
'title' => $node->name,
'icon' => $node->icon,
'keepAlive' => true,
'authList' => $authList,
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
'openType' => $node->open_type,
],
'children' => $children,
'children' => $children,
];
}
return $tree;
}
/**
* 获取用户所有权限code
*/