新增功能

This commit is contained in:
mzl
2025-07-10 14:06:01 +08:00
parent f6323f2513
commit 43d673ad9c
20 changed files with 738 additions and 169 deletions
@@ -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',
]);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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);
return $this->success($this->transformData($result), '角色列表获取成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage(), 1, $e->getCode());
}
}
/**
* 获取角色详情
*/
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(), 1, $e->getCode());
}
}
/**
* 增加角色
*/
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(), 1, $e->getCode());
}
}
/**
* 修改角色
*/
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(), 1, $e->getCode());
}
}
/**
* 删除角色
*/
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(), 1, $e->getCode());
}
}
}
+11 -9
View File
@@ -14,17 +14,19 @@ class AuthController extends BaseController
*/
protected $noNeedHeader = ['feishuAuth'];
protected $noNeedLogin = ['feishuScope', 'login', 'register'];
// 调试
public function feishuAuth(Request $request)
{
try {
$code = $request->all();
return $this->success($code);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
// 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)
{
+3 -2
View File
@@ -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;
+25
View File
@@ -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());
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?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();
}
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public function buildTree($resources, int $parentId = 0): array
{
$tree = [];
foreach ($resources as $node) {
if ($node->parent_id !== $parentId) continue;
// 子节点
$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,
'meta' => [
'title' => $node->name,
'icon' => $node->icon,
'keepAlive' => true,
'authList' => $authList,
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
],
'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();
}
}
+93
View File
@@ -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);
}
}
}
+27
View File
@@ -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;
}
}
+2
View File
@@ -41,6 +41,8 @@ class Role extends Model
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
// 角色下所有用户
public function users()
+21 -62
View File
@@ -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,7 +23,7 @@ class PermService
/**
* 获取用户所有资源(合并角色+直接分配),结果缓存
*/
public static function getUserResources(int $userId)
public function getUserResources(int $userId)
{
$cacheKey = self::cacheKey($userId);
$cached = Cache::get($cacheKey);
@@ -25,84 +31,37 @@ class PermService
return $cached;
}
$user = User::with(['roles.resources', 'resources'])->find($userId);
if (!$user) {
return collect();
}
// 调用 Logic 层获取资源
$resources = $this->logic->getUserResources($userId);
// 缓存结果
Cache::set($cacheKey, $resources, 3600);
$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;
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));
}
+109
View File
@@ -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));
}
}
+55
View File
@@ -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);
}
}
+29 -9
View File
@@ -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
];
}
}
}
+135
View File
@@ -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);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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',
'status' => 'require|in:0,1',
];
protected $message = [
'id.require' => '角色ID不能为空',
'system_id.require' => '系统ID不能为空',
'name.require' => '角色名称不能为空',
'name.max' => '角色名称不能超过64个字符',
'desc.max' => '描述不能超过128个字符',
'status.require' => '状态不能为空',
'status.in' => '状态值必须为0或1',
];
protected $scene = [
'role_create' => ['system_id', 'name', 'desc', 'status'],
'role_update' => ['id', 'system_id', 'name', 'desc', 'status'],
];
}
+2
View File
@@ -6,6 +6,8 @@ use support\Request;
class IndexController
{
protected $noNeedLogin = ['index'];
public function index(Request $request)
{
+8
View File
@@ -61,4 +61,12 @@ function getConfig($filePath = null)
return [];
}
return $config;
}
/*
* 调试用
*/
function dpr(\Throwable $e)
{
var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ===");
}
+1 -1
View File
@@ -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',
+2 -1
View File
@@ -16,6 +16,7 @@
return [
'' => [
\app\middleware\AccessControl::class,
\app\middleware\BasicData::class
\app\middleware\BasicData::class,
\app\middleware\AuthCheck::class
]
];
+16 -2
View File
@@ -34,8 +34,22 @@ Route::group('/common', function () {
// 调试
Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']);
// // 调试
// 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']);
});
});