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:
@@ -1,3 +0,0 @@
|
||||
User ← user_roles → Role ← role_resources → Resource (type=menu/button)
|
||||
↘︎
|
||||
role_permissions → Permission
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\ResourceServer;
|
||||
use GuzzleHttp\Psr7\Response as Psr7Response;
|
||||
use app\service\AuthService;
|
||||
use app\model\User;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
class AuthController_bak
|
||||
{
|
||||
|
||||
protected AuthorizationServer $authServer;
|
||||
protected ResourceServer $resourceServer;
|
||||
|
||||
public function __construct(AuthorizationServer $auth, ResourceServer $res)
|
||||
{
|
||||
$this->authServer = $auth;
|
||||
$this->resourceServer = $res;
|
||||
}
|
||||
|
||||
// 浏览器授权页
|
||||
public function authorize(ServerRequestInterface $request)
|
||||
{
|
||||
// 如果未登录,跳转登录页(自行实现)
|
||||
// 登录后,确认授权 -> issue auth code -> 重定向回 client
|
||||
|
||||
// …未登录重定向到登录页,登录后调用:
|
||||
// $authRequest = $this->authServer->validateAuthorizationRequest($request);
|
||||
// …用户同意授权…
|
||||
// return $this->authServer->completeAuthorizationRequest($authRequest, new Psr7Response());
|
||||
}
|
||||
|
||||
// 授权码换 Token
|
||||
public function token(ServerRequestInterface $request)
|
||||
{
|
||||
return $this->authServer->respondToAccessTokenRequest($request, new Psr7Response());
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
public function userinfo(ServerRequestInterface $request)
|
||||
{
|
||||
$psr = $this->resourceServer->validateAuthenticatedRequest($request);
|
||||
$userId = $psr->getAttribute('oauth_user_id');
|
||||
// 根据 $userId 查询用户表返回
|
||||
// return json(['id' => $userId, 'username' => 'demo']);
|
||||
|
||||
// 用 Webman 的 support\Response 返回 JSON 更方便
|
||||
return json([
|
||||
'id' => $userId,
|
||||
'username' => User::find($userId)->username,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function info(Request $request)
|
||||
{
|
||||
$userId = $request->session()->get('user_id');
|
||||
if (!$userId) {
|
||||
return json(['code'=>401,'msg'=>'未登录']);
|
||||
}
|
||||
|
||||
$user = User::with('roles')->find($userId);
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'data' => [
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'roles' => $user->roles->pluck('name')->all(),
|
||||
],
|
||||
'menus' => AuthService::getMenuTree($userId),
|
||||
'permissions' => AuthService::getPermissionCodes($userId),
|
||||
],
|
||||
'msg' => 'ok',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\validate\RoleValidate;
|
||||
use app\common\service\RoleService;
|
||||
use support\Request;
|
||||
|
||||
class RoleController extends \app\common\controller\BaseController
|
||||
{
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
public function list(Request $request)
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
$result = (new RoleService())->getRoleList($params);
|
||||
// 定义后处理器
|
||||
$postProcessor = function($formattedData) {
|
||||
if (isset($formattedData['created_at']))
|
||||
$formattedData['created_at'] = date('Y-m-d H:i:s', $formattedData['created_at']);
|
||||
if (isset($formattedData['updated_at']))
|
||||
$formattedData['updated_at'] = date('Y-m-d H:i:s', $formattedData['updated_at']);
|
||||
return $formattedData;
|
||||
};
|
||||
return $this->success($this->transformData($result, $postProcessor), '角色列表获取成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色详情
|
||||
*/
|
||||
public function detail(Request $request)
|
||||
{
|
||||
try {
|
||||
$roleId = $request->get('role_id');
|
||||
if (!$roleId) {
|
||||
return $this->error('缺少角色ID', 422);
|
||||
}
|
||||
$result = (new RoleService())->getRoleDetail($roleId);
|
||||
return $this->success($result, '角色详情获取成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加角色
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate(new RoleValidate(), $request->post(), 'role_create');
|
||||
$result = (new RoleService())->createRole($data);
|
||||
return $this->success($result, '角色创建成功');
|
||||
} catch (\think\exception\ValidateException $e) {
|
||||
return $this->error($e->getError(), 422);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改角色
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate(new RoleValidate(), $request->post(), 'role_update');
|
||||
$result = (new RoleService())->updateRole($data);
|
||||
return $this->success($result, '角色更新成功');
|
||||
} catch (\think\exception\ValidateException $e) {
|
||||
return $this->error($e->getError(), 422);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
try {
|
||||
$roleId = $request->get('role_id');
|
||||
if (!$roleId) {
|
||||
return $this->error('缺少角色ID', 422);
|
||||
}
|
||||
(new RoleService())->deleteRole($roleId);
|
||||
return $this->success([], '角色删除成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ class AuthController extends BaseController
|
||||
*/
|
||||
protected $noNeedHeader = ['feishuAuth'];
|
||||
|
||||
protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth'];
|
||||
|
||||
|
||||
// 调试
|
||||
public function feishuAuth(Request $request)
|
||||
@@ -58,46 +60,56 @@ class AuthController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function verify(Request $request)
|
||||
{
|
||||
try {
|
||||
$token = $request->post('access_token') ?: $request->header('authorization');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
$payload = (new AuthService())->verifyToken($token, $systemCode);
|
||||
return $this->success($payload, '验证成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage() ?: '无效或过期的token', 401);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(Request $request)
|
||||
{
|
||||
try {
|
||||
$refresh_token = $request->post('refresh_token');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$refresh_token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
$result = (new AuthService())->refreshToken($refresh_token, $systemCode);
|
||||
$data = $this->requestData($request);
|
||||
if (!$data['refresh_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
|
||||
$result = (new AuthService())->refreshToken($data['refresh_token'], $data['system_code']);
|
||||
return $this->success($result, '刷新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$token = $request->post('access_token') ?: $request->header('Authorization');
|
||||
$systemCode = $request->post('system_code') ?: $request->header('system_code');
|
||||
(new AuthService())->logout($token, $systemCode);
|
||||
return $this->success([], '已登出');
|
||||
}
|
||||
|
||||
public function blockToken(Request $request)
|
||||
{
|
||||
$token = $request->post('token');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
(new AuthService())->blockToken($token, $systemCode);
|
||||
return $this->success([], '已强制停用该token');
|
||||
}
|
||||
// public function perm(Request $request)
|
||||
// {
|
||||
// try {
|
||||
// $data = $this->requestData($request);
|
||||
// if (!$data['access_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
|
||||
// $result = (new AuthService())->logout($data['access_token'], $data['system_code']);
|
||||
// return $this->success($result, '已登出');
|
||||
// } catch (\Throwable $e) {}
|
||||
// }
|
||||
|
||||
// public function verify(Request $request)
|
||||
// {
|
||||
// try {
|
||||
// $token = $request->post('access_token') ?: $request->header('authorization');
|
||||
// $systemCode = $request->post('system_code');
|
||||
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
// $payload = (new AuthService())->verifyToken($token, $systemCode);
|
||||
// return $this->success($payload, '验证成功');
|
||||
// } catch (\Throwable $e) {
|
||||
// return $this->error($e->getMessage() ?: '无效或过期的token', 401);
|
||||
// }
|
||||
// }
|
||||
|
||||
// public function logout(Request $request)
|
||||
// {
|
||||
// $token = $request->post('access_token') ?: $request->header('Authorization');
|
||||
// $systemCode = $request->post('system_code') ?: $request->header('system_code');
|
||||
// (new AuthService())->logout($token, $systemCode);
|
||||
// return $this->success([], '已登出');
|
||||
// }
|
||||
|
||||
// public function blockToken(Request $request)
|
||||
// {
|
||||
// $token = $request->post('token');
|
||||
// $systemCode = $request->post('system_code');
|
||||
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
// (new AuthService())->blockToken($token, $systemCode);
|
||||
// return $this->success([], '已强制停用该token');
|
||||
// }
|
||||
}
|
||||
@@ -4,12 +4,13 @@ namespace app\common\controller;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use app\common\traits\DataTransformer;
|
||||
use think\Validate;
|
||||
use support\Request;
|
||||
|
||||
abstract class BaseController
|
||||
class BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
use ResponseTrait, DataTransformer;
|
||||
|
||||
protected mixed $validator = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use app\common\service\UserService;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function info(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$result = (new UserService())->getUserInfo($uid);
|
||||
return $this->success($result, '获取用户信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+146
-109
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\User;
|
||||
|
||||
class PermLogic
|
||||
{
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配)
|
||||
*/
|
||||
public function getUserResources(int $userId)
|
||||
{
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$all = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge($role->resources);
|
||||
}
|
||||
$all = $all->merge($user->resources);
|
||||
return $all->unique('id')->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单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;
|
||||
|
||||
// 拼接完整路径
|
||||
$currentPath = $parentPath;
|
||||
if ($currentPath && substr($currentPath, -1) === '/') {
|
||||
$currentPath = rtrim($currentPath, '/');
|
||||
}
|
||||
if (!empty($node->url)) {
|
||||
if (!$currentPath) {
|
||||
$currentPath = $node->url;
|
||||
} else {
|
||||
$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) => [
|
||||
'id' => $r->id,
|
||||
'title' => $r->name,
|
||||
'auth_mark' => $r->code,
|
||||
])->values()->all();
|
||||
|
||||
// 判断是否有子级菜单(非权限)
|
||||
$hasChildrenMenu = collect($resources)->where('parent_id', $node->id)
|
||||
->whereIn('type', ['directory', 'menu'])->isNotEmpty();
|
||||
|
||||
// component 字段规则:
|
||||
// 1. 存在子菜单,固定 '/index/index'
|
||||
// 2. 否则为当前完整路径(或其它你想要的)
|
||||
$componentValue = $hasChildrenMenu ? '/index/index' : $currentPath;
|
||||
|
||||
$tree[] = [
|
||||
'id' => $node->id,
|
||||
'name' => $node->name,
|
||||
'path' => $node->url,
|
||||
'component' => $componentValue,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => true,
|
||||
'authList' => $authList,
|
||||
'openType' => $node->open_type,
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限code
|
||||
*/
|
||||
public function getPermissionCodes(int $userId): array
|
||||
{
|
||||
$resources = $this->getUserResources($userId);
|
||||
return $resources
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\Role;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class RoleLogic
|
||||
{
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
public function getRoleList(array $params)
|
||||
{
|
||||
// 假设我们需要根据system_id进行过滤,其他过滤条件也可以添加
|
||||
$query = Role::query();
|
||||
|
||||
if (isset($params['system_id'])) {
|
||||
$query->where('system_id', $params['system_id']);
|
||||
}
|
||||
if (isset($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 添加分页
|
||||
$roles = $query->paginate($params['per_page'] ?? 15);
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色详情
|
||||
*/
|
||||
public function getRoleDetail(int $roleId)
|
||||
{
|
||||
$role = Role::find($roleId);
|
||||
if (!$role) {
|
||||
throw new BusinessException('角色不存在', 404);
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
*/
|
||||
public function createRole(array $data)
|
||||
{
|
||||
$role = new Role();
|
||||
$role->fill($data); // 根据字段填充数据
|
||||
try {
|
||||
$role->save();
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('创建角色失败:'.$e->getMessage());
|
||||
throw new BusinessException('创建角色失败', 500);
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*/
|
||||
public function updateRole(array $data)
|
||||
{
|
||||
$role = Role::find($data['id']);
|
||||
if (!$role) {
|
||||
throw new BusinessException('角色不存在', 404);
|
||||
}
|
||||
|
||||
$role->update($data); // 更新字段
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
public function deleteRole(int $roleId)
|
||||
{
|
||||
$role = Role::find($roleId);
|
||||
if (!$role) {
|
||||
throw new BusinessException('角色不存在', 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$role->delete();
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('删除角色失败:'.$e->getMessage());
|
||||
throw new BusinessException('删除角色失败', 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\User;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class UserLogic
|
||||
{
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function userInfo(int $uid)
|
||||
{
|
||||
$user = User::find($uid);
|
||||
|
||||
if (!$user) {
|
||||
throw new BusinessException('账户不存在', 404);
|
||||
}
|
||||
|
||||
if ($user->status != 1) {
|
||||
throw new BusinessException('账号已禁用', 403);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,8 @@ class Role extends Model
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
// 角色下所有用户
|
||||
public function users()
|
||||
|
||||
@@ -58,8 +58,12 @@ class User extends Model
|
||||
'phone',
|
||||
'email',
|
||||
'status',
|
||||
'system_code',
|
||||
'corp_id'
|
||||
'corp_id',
|
||||
'user_type',
|
||||
'union_id',
|
||||
'user_id',
|
||||
'open_id',
|
||||
'tenant_key'
|
||||
];
|
||||
|
||||
// 用户所有角色
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* user_feishu_app 用户-飞书应用
|
||||
* @property integer $id ID(主键)
|
||||
* @property integer $uid 用户ID
|
||||
* @property integer $corp_id 公司ID
|
||||
* @property string $name 名称
|
||||
* @property string $mobile 手机
|
||||
* @property string $union_id ISV的唯一标识
|
||||
* @property string $user_id 用户 user_id
|
||||
* @property string $open_id 应用内的唯一标识
|
||||
* @property string $tenant_key 当前企业标识
|
||||
* @property integer $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class UserFeishuApp_bak extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user_feishu_app';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'uid',
|
||||
'corp_id',
|
||||
'name',
|
||||
'mobile',
|
||||
'union_id',
|
||||
'user_id',
|
||||
'open_id',
|
||||
'tenant_key',
|
||||
'sort',
|
||||
'status'
|
||||
];
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class AuthService
|
||||
|
||||
return match ($loginType) {
|
||||
'feishu' => $logic->feishuLogin($data),
|
||||
'qrcode' => $logic->qrCodeLogin($data),
|
||||
// 'qrcode' => $logic->qrCodeLogin($data),
|
||||
default => $logic->uPwdLogin($data),
|
||||
};
|
||||
}
|
||||
@@ -86,4 +86,13 @@ class AuthService
|
||||
{
|
||||
(new AuthLogic())->blockToken($token, $systemCode);
|
||||
}
|
||||
|
||||
|
||||
// public function perms()
|
||||
// {
|
||||
// $permService = new PermService();
|
||||
// $menuTree = $permService->getMenuTree($uid);
|
||||
// $permissionCodes = $permService->getPermissionCodes($uid);
|
||||
// return (new PermService())->getMenuTree($this->userId)
|
||||
// }
|
||||
}
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\logic\PermLogic;
|
||||
use support\Cache;
|
||||
|
||||
class PermService
|
||||
{
|
||||
protected $logic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logic = (new PermLogic());
|
||||
}
|
||||
|
||||
// 用户资源缓存key
|
||||
protected static function cacheKey(int $userId): string
|
||||
@@ -17,92 +23,42 @@ class PermService
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配),结果缓存
|
||||
*/
|
||||
public static function getUserResources(int $userId)
|
||||
public function getUserResources(int $userId)
|
||||
{
|
||||
$cacheKey = self::cacheKey($userId);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$all = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge($role->resources);
|
||||
}
|
||||
$all = $all->merge($user->resources);
|
||||
$unique = $all->unique('id')->values();
|
||||
|
||||
Cache::set($cacheKey, $unique, 3600);
|
||||
return $unique;
|
||||
// 调用 Logic 层获取资源
|
||||
$resources = $this->logic->getUserResources($userId);
|
||||
// 缓存结果
|
||||
Cache::set($cacheKey, $resources, 3600);
|
||||
return $resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式
|
||||
*/
|
||||
public static function getMenuTree(int $userId): array
|
||||
public function getMenuTree(int $userId): array
|
||||
{
|
||||
$resources = self::getUserResources($userId);
|
||||
$menus = $resources
|
||||
->filter(fn($r) => in_array($r->type, ['directory','menu']))
|
||||
->values();
|
||||
return self::buildTree($menus, $resources);
|
||||
}
|
||||
|
||||
protected static function buildTree($nodes, $allResources, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->parent_id !== $parentId) continue;
|
||||
|
||||
$children = self::buildTree($nodes, $allResources, $node->id);
|
||||
|
||||
$authList = $allResources
|
||||
->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,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => true,
|
||||
'authList' => $authList,
|
||||
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
// 调用 Logic 层处理菜单构建
|
||||
$resources = $this->getUserResources($userId);
|
||||
return $this->logic->buildTree($resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限code
|
||||
*/
|
||||
public static function getPermissionCodes(int $userId): array
|
||||
public function getPermissionCodes(int $userId): array
|
||||
{
|
||||
return self::getUserResources($userId)
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
return $this->logic->getPermissionCodes($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除资源相关缓存
|
||||
*/
|
||||
public static function clearCache(int $userId): void
|
||||
public function clearCache(int $userId): void
|
||||
{
|
||||
Cache::delete(self::cacheKey($userId));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use support\Cache;
|
||||
|
||||
class PermService_bak
|
||||
{
|
||||
|
||||
// 用户资源缓存key
|
||||
protected static function cacheKey(int $userId): string
|
||||
{
|
||||
return "user_resources_{$userId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配),结果缓存
|
||||
*/
|
||||
public static function getUserResources(int $userId)
|
||||
{
|
||||
$cacheKey = self::cacheKey($userId);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$all = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge($role->resources);
|
||||
}
|
||||
$all = $all->merge($user->resources);
|
||||
$unique = $all->unique('id')->values();
|
||||
|
||||
Cache::set($cacheKey, $unique, 3600);
|
||||
return $unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式
|
||||
*/
|
||||
public static function getMenuTree(int $userId): array
|
||||
{
|
||||
$resources = self::getUserResources($userId);
|
||||
$menus = $resources
|
||||
->filter(fn($r) => in_array($r->type, ['directory','menu']))
|
||||
->values();
|
||||
return self::buildTree($menus, $resources);
|
||||
}
|
||||
|
||||
protected static function buildTree($nodes, $allResources, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->parent_id !== $parentId) continue;
|
||||
|
||||
$children = self::buildTree($nodes, $allResources, $node->id);
|
||||
|
||||
$authList = $allResources
|
||||
->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,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => true,
|
||||
'authList' => $authList,
|
||||
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限code
|
||||
*/
|
||||
public static function getPermissionCodes(int $userId): array
|
||||
{
|
||||
return self::getUserResources($userId)
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除资源相关缓存
|
||||
*/
|
||||
public static function clearCache(int $userId): void
|
||||
{
|
||||
Cache::delete(self::cacheKey($userId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\RoleLogic;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
protected $roleLogic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->roleLogic = (new RoleLogic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
public function getRoleList(array $params)
|
||||
{
|
||||
return $this->roleLogic->getRoleList($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色详情
|
||||
*/
|
||||
public function getRoleDetail(int $roleId)
|
||||
{
|
||||
return $this->roleLogic->getRoleDetail($roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
*/
|
||||
public function createRole(array $data)
|
||||
{
|
||||
return $this->roleLogic->createRole($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*/
|
||||
public function updateRole(array $data)
|
||||
{
|
||||
return $this->roleLogic->updateRole($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
public function deleteRole(int $roleId)
|
||||
{
|
||||
return $this->roleLogic->deleteRole($roleId);
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,39 @@
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\model\User;
|
||||
use app\common\logic\UserLogic;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public static function getInfo($userId)
|
||||
|
||||
protected $logic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$user = User::with('roles')->find($userId);
|
||||
if (!$user) return null;
|
||||
$this->logic = (new UserLogic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息(包括菜单和权限)
|
||||
*/
|
||||
public function getUserInfo(int $uid)
|
||||
{
|
||||
$user = $this->logic->userInfo($uid);
|
||||
$permService = new PermService();
|
||||
$menuTree = $permService->getMenuTree($uid);
|
||||
$permissionCodes = $permService->getPermissionCodes($uid);
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'roles' => $user->roles->pluck('name')->toArray(),
|
||||
// 可扩展其他字段
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'avatar' => $user->avatar,
|
||||
'phone' => $user->phone,
|
||||
'email' => $user->email,
|
||||
'status' => $user->status,
|
||||
'roles' => $user->roles->pluck('name')
|
||||
],
|
||||
'menu' => $menuTree,
|
||||
'permissions' => $permissionCodes
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait DataTransformer
|
||||
{
|
||||
/**
|
||||
* 核心方法:对任意数据进行转换并返回数组
|
||||
*
|
||||
* @param mixed $rawData 原始数据,可为 Paginator / Model / Collection / array 等
|
||||
* @param callable|null $postProcessor 可选的后置处理回调,接受处理完的数组再返回新的数组
|
||||
* @param callable|null $fieldFormatter 可选的字段命名转换回调,如 snake_case
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function transformData(
|
||||
mixed $rawData,
|
||||
?callable $postProcessor = null,
|
||||
?callable $fieldFormatter = null
|
||||
): array {
|
||||
try {
|
||||
$fieldFormatter = $fieldFormatter ?: fn($field) => $field;
|
||||
|
||||
if ($rawData instanceof LengthAwarePaginator) {
|
||||
$formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Model) {
|
||||
$formatted = $this->formatModel($rawData, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Collection) {
|
||||
$formatted = $this->formatCollection($rawData, $fieldFormatter);
|
||||
} elseif (is_array($rawData)) {
|
||||
$formatted = $this->formatArray($rawData, $fieldFormatter);
|
||||
} else {
|
||||
$formatted = (array) $rawData;
|
||||
}
|
||||
|
||||
if (is_callable($postProcessor)) {
|
||||
$formatted = $postProcessor($formatted);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'error' => true,
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化分页数据:将每条记录也进行 transformData 递归处理
|
||||
*
|
||||
* @param LengthAwarePaginator $paginator
|
||||
* @param callable|null $postProcessor
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatPaginated(
|
||||
LengthAwarePaginator $paginator,
|
||||
?callable $postProcessor,
|
||||
callable $fieldFormatter
|
||||
): array {
|
||||
$list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) {
|
||||
return $this->transformData($item, $postProcessor, $fieldFormatter);
|
||||
})->toArray();
|
||||
|
||||
return [
|
||||
'records' => $list,
|
||||
'total' => $paginator->total(),
|
||||
'current' => $paginator->currentPage(),
|
||||
'size' => $paginator->perPage()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Eloquent 模型:递归嵌套关联
|
||||
*
|
||||
* @param Model $model
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatModel(Model $model, callable $fieldFormatter): array
|
||||
{
|
||||
$formatted = [];
|
||||
|
||||
// 主表字段
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
$formatted[$fieldFormatter($key)] = $value;
|
||||
}
|
||||
|
||||
// 关联数据,递归嵌套
|
||||
foreach ($model->getRelations() as $relation => $relationData) {
|
||||
if ($relationData instanceof Model) {
|
||||
// 单个关联模型
|
||||
$formatted[$relation] = $this->formatModel($relationData, $fieldFormatter);
|
||||
} elseif ($relationData instanceof Collection) {
|
||||
// 关联集合
|
||||
$formatted[$relation] = $relationData
|
||||
->map(fn($item) => $this->transformData($item, null, $fieldFormatter))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化集合
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatCollection(Collection $collection, callable $fieldFormatter): array
|
||||
{
|
||||
return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化普通数组
|
||||
*
|
||||
* @param array $array
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatArray(array $array, callable $fieldFormatter): array
|
||||
{
|
||||
return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class RoleValidate extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'id' => 'require|integer',
|
||||
'system_id' => 'require|integer',
|
||||
'name' => 'require|max:64',
|
||||
'desc' => 'max:128',
|
||||
'sort' => 'integer',
|
||||
'status' => 'in:0,1',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'id.require' => '角色ID不能为空',
|
||||
'system_id.require' => '系统ID不能为空',
|
||||
'name.require' => '角色名称不能为空',
|
||||
'name.max' => '角色名称不能超过64个字符',
|
||||
'desc.max' => '描述不能超过128个字符',
|
||||
'sort.integer' => '排序为数字',
|
||||
'status.in' => '状态值必须为0或1',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'role_create' => ['system_id', 'name', 'desc', 'sort'],
|
||||
'role_update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'],
|
||||
];
|
||||
}
|
||||
@@ -6,6 +6,8 @@ use support\Request;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['index', 'view'];
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
+51
-38
@@ -2,8 +2,6 @@
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
||||
|
||||
use support\exception\BusinessException;
|
||||
|
||||
/**
|
||||
* 标准化账号参数
|
||||
@@ -11,54 +9,69 @@ use support\exception\BusinessException;
|
||||
* @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)) {
|
||||
if (!function_exists('normalizeAccount')) {
|
||||
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];
|
||||
}
|
||||
|
||||
// 如果既不是邮箱也不是手机号,根据业务需求处理
|
||||
// 这里默认按手机号处理(可根据实际需求调整)
|
||||
return ['phone' => $cleanPhone];
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共html页面
|
||||
*/
|
||||
function htmlpage()
|
||||
{
|
||||
static $html;
|
||||
if (!$html) {
|
||||
$html = file_get_contents(public_path('mzl.html'));
|
||||
if (!function_exists('htmlpage')) {
|
||||
function htmlpage()
|
||||
{
|
||||
static $html;
|
||||
if (!$html) {
|
||||
$html = file_get_contents(public_path('mzl.html'));
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
function getConfig($filePath = null)
|
||||
{
|
||||
// 构建文件路径
|
||||
$config_file = app_path($filePath);
|
||||
// 检查文件是否存在
|
||||
if (!file_exists($config_file)) {
|
||||
return [];
|
||||
if (!function_exists('getConfig')) {
|
||||
function getConfig($filePath = null)
|
||||
{
|
||||
// 构建文件路径
|
||||
$config_file = app_path($filePath);
|
||||
// 检查文件是否存在
|
||||
if (!file_exists($config_file)) {
|
||||
return [];
|
||||
}
|
||||
// 包含文件并获取返回的数组
|
||||
$config = include $config_file;
|
||||
// 验证是否为数组
|
||||
if (!is_array($config)) {
|
||||
return [];
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
// 包含文件并获取返回的数组
|
||||
$config = include $config_file;
|
||||
// 验证是否为数组
|
||||
if (!is_array($config)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/*
|
||||
* 调试用
|
||||
*/
|
||||
if (!function_exists('dpr')) {
|
||||
function dpr(\Throwable $e)
|
||||
{
|
||||
var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ===");
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class AuthCheck implements MiddlewareInterface
|
||||
return $handler($request);
|
||||
} else {
|
||||
try {
|
||||
$authHeader = $request->header('Authorization');
|
||||
$authHeader = $request->header('Fdesk-Auth-Token');
|
||||
if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
|
||||
$body = [
|
||||
'status' => 'fail',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// const REDIRECT_URI = encodeURIComponent('https://example.com/api/oauth/callback/feishu');
|
||||
// const STATE = '随机串_防CSRF';
|
||||
// // 用于跳转获取 code 的 URL
|
||||
const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`;
|
||||
const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a72f13d6c022500d&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`;
|
||||
// const GOTO = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&scope=contact:user.base:readonly contact:contact.base:readonly contact:user.employee_id:readonly im:message:send_as_bot&state=xinyouhe`;
|
||||
// 2. 创建二维码实例
|
||||
const QRLoginObj = QRLogin({
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@
|
||||
"topthink/think-validate": "^3.0",
|
||||
"shopwwi/webman-filesystem": "1.1",
|
||||
"shopwwi/filesystem-oss": "^1.0",
|
||||
"webman/redis-queue": "^1.3",
|
||||
"webman/redis-queue": "^2.1",
|
||||
"symfony/translation": "^7.3"
|
||||
},
|
||||
"suggest": {
|
||||
|
||||
Generated
+12
-15
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "acb636fcb8774558a04bd674f9f95f55",
|
||||
"content-hash": "30cc3cbb162022972a21efed9b69d1e1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aliyuncs/oss-sdk-php",
|
||||
@@ -6155,26 +6155,23 @@
|
||||
},
|
||||
{
|
||||
"name": "webman/redis-queue",
|
||||
"version": "v1.3.1",
|
||||
"version": "v2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webman-php/redis-queue.git",
|
||||
"reference": "6fd491b19acb006d60931724aa2577a046ccb612"
|
||||
"reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webman-php/redis-queue/zipball/6fd491b19acb006d60931724aa2577a046ccb612",
|
||||
"reference": "6fd491b19acb006d60931724aa2577a046ccb612",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
"url": "https://api.github.com/repos/webman-php/redis-queue/zipball/12e1c9915ec64c91c0c9c255606bd0c7de27334c",
|
||||
"reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"workerman/redis-queue": "^1.0"
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.1",
|
||||
"workerman/redis-queue": "^1.2",
|
||||
"workerman/webman-framework": "^2.1 || dev-master"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -6186,9 +6183,9 @@
|
||||
"description": "Redis message queue plugin for webman.",
|
||||
"support": {
|
||||
"issues": "https://github.com/webman-php/redis-queue/issues",
|
||||
"source": "https://github.com/webman-php/redis-queue/tree/v1.3.1"
|
||||
"source": "https://github.com/webman-php/redis-queue/tree/v2.1.0"
|
||||
},
|
||||
"time": "2024-02-28T07:21:52+00:00"
|
||||
"time": "2025-01-24T12:56:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "workerman/coroutine",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
return [
|
||||
'' => [
|
||||
\app\middleware\AccessControl::class,
|
||||
\app\middleware\BasicData::class
|
||||
\app\middleware\BasicData::class,
|
||||
\app\middleware\AuthCheck::class
|
||||
]
|
||||
];
|
||||
+16
-2
@@ -31,12 +31,26 @@ Route::group('/common', function () {
|
||||
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('/refresh/token', [app\common\controller\AuthController::class, 'refresh']);
|
||||
|
||||
|
||||
|
||||
// 调试
|
||||
// // 调试
|
||||
Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']);
|
||||
});
|
||||
|
||||
Route::group('/user', function () {
|
||||
Route::any('/info', [app\common\controller\UserController::class, 'info']);
|
||||
});
|
||||
});
|
||||
|
||||
Route::group('/admin', function () {
|
||||
Route::group('/role', function () {
|
||||
Route::any('/list', [app\admin\controller\RoleController::class, 'list']);
|
||||
Route::any('/detail', [app\admin\controller\RoleController::class, 'detail']);
|
||||
Route::any('/create', [app\admin\controller\RoleController::class, 'create']);
|
||||
Route::any('/update', [app\admin\controller\RoleController::class, 'update']);
|
||||
Route::any('/delete', [app\admin\controller\RoleController::class, 'delete']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ return [
|
||||
'basi_cdata' => [
|
||||
'/',
|
||||
'/index/index',
|
||||
// '/index/view'
|
||||
'/index/view'
|
||||
]
|
||||
];
|
||||
Reference in New Issue
Block a user