This commit is contained in:
zimoyin
2026-04-03 11:32:19 +08:00
parent 4c841b9dbf
commit 1a84e92384
30 changed files with 403 additions and 1030 deletions
-395
View File
@@ -1,395 +0,0 @@
<?php
namespace plugin\admin\app\common;
use app\utils\Logger;
class AccessDataUtil
{
public static function admin_id(): ?int
{
return runCatching(fn() => admin()['id'], "无法获取到管理员登录信息")->getOrNull();
}
public static function admin_username(): ?string
{
return runCatching(fn() => admin()['username'], "无法获取到管理员登录信息")->getOrNull();
}
public static function admin_nickname(): ?string
{
return runCatching(fn() => admin()['nickname'], "无法获取到管理员登录信息")->getOrNull();
}
/**
* 获取&设置权限树
*/
public static function data_permission_tree(?array $data = null): DataPermissionInterface
{
// 获取 session
try {
$data_permission_tree = session()("data_permission_tree");
if (!empty($data_permission_tree)) return new DataPermission($data_permission_tree);
} catch (\Exception $e) {
Logger::error("获取 session data_permission_tree key 失败",$e);
}
// 如果不存在就去数据库查表
return [];
}
}
/**
* 数据权限基础接口
* 定义所有权限类必须实现的核心方法
*/
interface DataPermissionInterface
{
/**
* 获取权限唯一标识ID
*/
public function id(): int;
/**
* 获取权限名称
*/
public function name(): string;
/**
* 转为array
* @return array
*/
public function toArray(): array;
}
/**
* 科室权限类(你需要的 DepartmentPxx
* 最小权限单元:管理科室下的具体权限项(如查看、编辑、删除等)
*/
class DepartmentPermission implements DataPermissionInterface
{
// 科室ID
private int $deptId;
// 科室名称
private string $deptName;
public function __construct(int $deptId, string $deptName)
{
$this->deptId = $deptId;
$this->deptName = $deptName;
}
/**
* 实现接口:获取科室ID
*/
public function id(): int
{
return $this->deptId;
}
/**
* 实现接口:获取科室名称
*/
public function name(): string
{
return $this->deptName;
}
public function toArray(): array
{
$array = [];
$array['id'] = $this->deptId;
$array['name'] = $this->deptName;
return $array;
}
}
/**
* 医院权限类
* 内部管理:多个科室权限对象(DepartmentPermission
*/
class HospitalPermission implements DataPermissionInterface
{
// 医院ID
private int $hospitalId;
// 医院名称
private string $hospitalName;
// 科室权限集合:科室ID => DepartmentPermission对象
private array $deptPermissions = [];
public function __construct(int $hospitalId, string $hospitalName)
{
$this->hospitalId = $hospitalId;
$this->hospitalName = $hospitalName;
}
/**
* 实现接口:获取医院ID
*/
public function id(): int
{
return $this->hospitalId;
}
/**
* 实现接口:获取医院名称
*/
public function name(): string
{
return $this->hospitalName;
}
/**
* 添加单个科室权限
*/
public function addDept(DepartmentPermission $deptPermission): void
{
$this->deptPermissions[$deptPermission->id()] = $deptPermission;
}
/**
* 批量添加科室权限
*/
public function addDepts(array $deptPermissions): void
{
foreach ($deptPermissions as $dept) {
if ($dept instanceof DepartmentPermission) {
$this->addDept($dept);
}
}
}
/**
* 获取单个科室权限
*/
public function getDept(int $deptId): ?DepartmentPermission
{
return $this->deptPermissions[$deptId] ?? null;
}
/**
* 判断是否包含某个科室
*/
public function hasDept(int $deptId): bool
{
return isset($this->deptPermissions[$deptId]);
}
/**
* 删除指定科室权限
*/
public function removeDept(int $deptId): void
{
unset($this->deptPermissions[$deptId]);
}
/**
* 获取所有科室权限
*/
public function getAllDepts(): array
{
return $this->deptPermissions;
}
/**
* 清空所有科室权限
*/
public function clearDepts(): void
{
$this->deptPermissions = [];
}
public function toArray(): array
{
$array = [];
$depts = [];
$array['id'] = $this->id();
$array['name'] = $this->name();
foreach ($this->deptPermissions as $deptPermission) {
$depts[] = $deptPermission->toArray();
}
$array['depts'] = $depts;
return $array;
}
}
/**
* 顶级数据权限类
*/
class DataPermission implements DataPermissionInterface
{
/**
* 从传入数组中获取的 ID 与名称
*/
private int $id;
private string $name;
/**
* 多医院集合:hospitalId => HospitalPermission
*/
private array $hospitalPermissions = [];
/**
* 构造方法:接收 toArray() 输出的权限数组,自动还原为多医院对象结构
* @param array $data_permission_tree 传入 DataPermission::toArray() 输出的数组
*/
public function __construct(array $data_permission_tree)
{
$this->clear();
// 读取传入的 id 和 name,不硬编码
$this->id = isset($data_permission_tree['id']) ? (int)$data_permission_tree['id'] : 0;
$this->name = isset($data_permission_tree['name']) ? (string)$data_permission_tree['name'] : '';
// 获取所有医院数组
$hospitalList = $data_permission_tree['hospitals'] ?? [];
if (!is_array($hospitalList) || empty($hospitalList)) {
return;
}
// 循环还原所有医院 + 下属科室
foreach ($hospitalList as $hospitalData) {
if (
!is_array($hospitalData)
|| empty($hospitalData['id'])
|| empty($hospitalData['name'])
) {
continue;
}
// 创建医院权限
$hospitalId = (int)$hospitalData['id'];
$hospitalName = (string)$hospitalData['name'];
$hospitalPermission = new HospitalPermission($hospitalId, $hospitalName);
// 添加下属科室
$deptList = $hospitalData['depts'] ?? [];
foreach ($deptList as $deptData) {
if (empty($deptData['id']) || empty($deptData['name'])) {
continue;
}
$deptPermission = new DepartmentPermission(
(int)$deptData['id'],
(string)$deptData['name']
);
$hospitalPermission->addDept($deptPermission);
}
// 存入多医院集合
$this->hospitalPermissions[$hospitalId] = $hospitalPermission;
}
}
/**
* 实现接口:获取顶级权限ID(来自传入数组)
*/
public function id(): int
{
return $this->id;
}
/**
* 实现接口:获取顶级权限名称(来自传入数组)
*/
public function name(): string
{
return $this->name;
}
/**
* 添加单个医院权限
*/
public function addHospital(HospitalPermission $hospitalPermission): void
{
$this->hospitalPermissions[$hospitalPermission->id()] = $hospitalPermission;
}
/**
* 批量添加医院
*/
public function addHospitals(array $hospitalPermissions): void
{
foreach ($hospitalPermissions as $hospital) {
if ($hospital instanceof HospitalPermission) {
$this->addHospital($hospital);
}
}
}
/**
* 获取单个医院
*/
public function getHospital(int $hospitalId): ?HospitalPermission
{
return $this->hospitalPermissions[$hospitalId] ?? null;
}
/**
* 判断是否包含某个医院
*/
public function hasHospital(int $hospitalId): bool
{
return isset($this->hospitalPermissions[$hospitalId]);
}
/**
* 删除指定医院
*/
public function removeHospital(int $hospitalId): void
{
unset($this->hospitalPermissions[$hospitalId]);
}
/**
* 获取所有医院
*/
public function getAllHospitals(): array
{
return $this->hospitalPermissions;
}
/**
* 获取所有医院ID
*/
public function getAllHospitalIds(): array
{
return array_keys($this->hospitalPermissions);
}
/**
* 判断是否有医院权限
*/
public function has(): bool
{
return !empty($this->hospitalPermissions);
}
/**
* 清空所有医院权限
*/
public function clear(): void
{
$this->hospitalPermissions = [];
}
/**
* 转为array(完美支持构造方法还原)
*/
public function toArray(): array
{
$array = [
'id' => $this->id(),
'name' => $this->name(),
'hospitals' => []
];
foreach ($this->hospitalPermissions as $hospital) {
$array['hospitals'][] = $hospital->toArray();
}
return $array;
}
}
-24
View File
@@ -1,24 +0,0 @@
<?php
namespace plugin\admin\app\common;
use app\utils\Logger;
class ExUtil
{
/**
* 执行闭包,捕获异常并记录日志,不中断程序
* @param callable $func 要执行的逻辑
* @param string $errorMsg 异常日志描述
* @return mixed|null 执行结果 / 异常返回null
*/
public static function runCatching(callable $func, string $errorMsg = '执行异常'): mixed
{
try {
return $func();
} catch (\Throwable $e) {
Logger::error($errorMsg, $e);
return null;
}
}
}
+53 -9
View File
@@ -2,20 +2,16 @@
namespace plugin\admin\app\controller;
use Doctrine\Inflector\InflectorFactory;
use Illuminate\Database\Schema\Blueprint;
use plugin\admin\app\common\Layui;
use plugin\admin\app\common\Util;
use plugin\admin\app\model\Role;
use plugin\admin\app\model\Rule;
use plugin\admin\app\model\Option;
use support\exception\BusinessException;
use support\Request;
use plugin\admin\app\model\OpmMwDepartment;
use plugin\admin\app\model\OpmMwHospital;
use app\utils\Logger;
use support\Response;
use Throwable;
class TestController extends Crud
{
/**
* 浏览
* @return Response
@@ -25,4 +21,52 @@ class TestController extends Crud
{
return raw_view('test/index');
}
/**
* 获取医院科室树形结构
* @return Response
*/
public function tree(): Response
{
// 1. 查询所有医院
$hospitals = OpmMwHospital::where('is_true', 1) // 只查有效医院
->withDataPermission()
->orderBy('id')
->get();
// 2. 组装树结构
$treeData = [];
foreach ($hospitals as $hospital) {
// 医院节点
$hospitalNode = [
'id' => $hospital->id,
'title' => $hospital->organ_name, // 医院名称
'children' => []
];
// 3. 查询该医院下的所有科室
$departments = OpmMwDepartment::where('organ_id', $hospital->id)
->withDataPermission()
->orderBy('sort_id')
->get();
// 4. 组装科室节点
foreach ($departments as $dept) {
$hospitalNode['children'][] = [
'id' => $dept->id,
'title' => $dept->dept_name // 科室名称
];
}
$treeData[] = $hospitalNode;
}
// 5. 返回 Layui 树需要的格式
return json([
'code' => 0,
'msg' => 'success',
'data' => $treeData
]);
}
}
@@ -1,57 +0,0 @@
<?php
namespace plugin\admin\app\middleware;
use app\utils\Logger;
use plugin\admin\api\Auth;
use ReflectionException;
use support\exception\BusinessException;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 账户数据权限表
*/
class AccessDataControl implements MiddlewareInterface
{
/**
* @param Request $request
* @param callable $handler
* @return Response
* @throws ReflectionException|BusinessException
*/
public function process(Request $request, callable $handler): Response
{
// 获取当前登录的管理员
$admin = runCatching(fn() => admin(), "无法获取到管理员登录信息")->getOrNull();
$controller = $request->controller;
$action = $request->action;
$code = 0;
$msg = '';
if (!Auth::canAccess($controller, $action, $code, $msg)) {
if ($request->expectsJson()) {
$response = json(['code' => $code, 'msg' => $msg, 'data' => []]);
} else {
if ($code === 401) {
$response = admin_error_401_script();
} else {
$request->app = '';
$request->plugin = 'admin';
$response = view('common/error/403')->withStatus(403);
}
}
} else {
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
}
return $response;
}
}
@@ -0,0 +1,50 @@
<?php
namespace plugin\admin\app\middleware;
use app\config\Config;
use app\utils\Logger;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* 请求耗时监测中间件
* 记录从请求进入到响应返回的全流程耗时
*/
class RequestTimeMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
// 1. 请求进入时记录开始时间(微秒级,高精度)
$startTime = microtime(true);
if (Config::getInstance()->enableRequestTimeLog) {
Logger::debug("{} {}", [$request->method(), $request->fullUrl()]);
}
// 2. 执行后续中间件/控制器业务逻辑
$response = $handler($request);
if (Config::getInstance()->enableRequestTimeLog) {
// 3. 请求处理完成后计算耗时
$endTime = microtime(true);
$costTime = round(($endTime - $startTime) * 1000, 2); // 转换为毫秒,保留2位小数
// 获取请求信息(方便日志定位)
$requestMethod = $request->method();
$requestUrl = $request->fullUrl();
$clientIp = $request->getRealIp();
// 4. 打印耗时日志
Logger::debug("[请求耗时] {} {} | IP: {} | 耗时: {} ms", [
$requestMethod,
$requestUrl,
$clientIp,
$costTime
]);
}
// 5. 返回响应给客户端
return $response;
}
}
+187
View File
@@ -2,10 +2,16 @@
namespace plugin\admin\app\model;
use app\utils\Logger;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Builder;
use support\Db;
use support\Model;
/**
* @method static \Illuminate\Database\Eloquent\Builder|static withDataPermission()
*/
class Base extends Model
{
/**
@@ -23,4 +29,185 @@ class Base extends Model
{
return $date->format('Y-m-d H:i:s');
}
public function scopeWithDataPermission(Builder $query): Builder
{
$admin = runCatching(fn() => admin(),"无法获取登录状态")->getOrDefault([]);
$hospitalRaw = trim((string)($admin['hospitals'] ?? ''));
$departmentRaw = trim((string)($admin['departments'] ?? ''));
if ($hospitalRaw === '*' && $departmentRaw === '*') {
return $query;
}
$hospitalIds = $this->parseVisibleIds($hospitalRaw);
$departmentIds = $this->parseVisibleIds($departmentRaw);
if (empty($hospitalIds) && empty($departmentIds)) {
return $query->whereRaw('1 = 0');
}
$baseQuery = $query->getQuery();
// 主表
[$fromTable, $fromAlias] = $this->parseTableAlias((string)($baseQuery->from ?? ''));
// 识别 join 里的医院/科室表别名
$hospitalAlias = $this->findTableAlias($baseQuery, 'opm_mw_hospital');
$departmentAlias = $this->findTableAlias($baseQuery, 'opm_mw_department');
Logger::debug('scopeWithDataPermission fromTable:{} fromAlias:{} hospitalAlias:{} departmentAlias:{} hospitalIds:{} departmentIds:{}', [
$fromTable,
$fromAlias,
$hospitalAlias,
$departmentAlias,
$hospitalIds,
$departmentIds,
]);
// 1) 主表就是医院表:只加医院权限
if ($fromTable === 'opm_mw_hospital' && $hospitalAlias) {
if (empty($hospitalIds)) {
return $query->whereRaw('1 = 0');
}
return $query->whereIn("{$hospitalAlias}.id", $hospitalIds);
}
// 2) 主表就是科室表:只加科室权限
if ($fromTable === 'opm_mw_department' && $departmentAlias) {
return $query->where(function (Builder $q) use ($departmentAlias, $hospitalIds, $departmentIds) {
$has = false;
// 科室权限:department.id
if (!empty($departmentIds)) {
$q->whereIn("{$departmentAlias}.id", $departmentIds);
$has = true;
}
// 医院权限:department.organ_id
if (!empty($hospitalIds)) {
if ($has) {
$q->whereIn("{$departmentAlias}.organ_id", $hospitalIds);
} else {
$q->orWhereIn("{$departmentAlias}.organ_id", $hospitalIds);
}
$has = true;
}
if (!$has) {
$q->whereRaw('1 = 0');
}
});
}
// 3) 主表不是这两个,但 join 里有它们:按别名补条件
return $query->where(function (Builder $q) use (
$hospitalAlias,
$departmentAlias,
$hospitalIds,
$departmentIds
) {
$hasAny = false;
if ($hospitalAlias && !empty($hospitalIds)) {
$q->whereIn("{$hospitalAlias}.id", $hospitalIds);
$hasAny = true;
}
if ($departmentAlias) {
$q->where(function (Builder $dq) use ($departmentAlias, $hospitalIds, $departmentIds) {
$has = false;
if (!empty($departmentIds)) {
$dq->whereIn("{$departmentAlias}.id", $departmentIds);
$has = true;
}
if (!empty($hospitalIds)) {
if ($has) {
$dq->orWhereIn("{$departmentAlias}.organ_id", $hospitalIds);
} else {
$dq->whereIn("{$departmentAlias}.organ_id", $hospitalIds);
}
$has = true;
}
if (!$has) {
$dq->whereRaw('1 = 0');
}
});
$hasAny = true;
}
if (!$hasAny) {
$q->whereRaw('1 = 0');
}
});
}
/**
* 解析 "table as t" / "table t"
*/
protected function parseTableAlias(string $table): array
{
$table = trim(str_replace('`', '', $table));
if ($table === '') {
return ['', ''];
}
if (stripos($table, ' as ') !== false) {
[$name, $alias] = preg_split('/\s+as\s+/i', $table, 2);
return [trim($name), trim($alias)];
}
$parts = preg_split('/\s+/', $table);
if (count($parts) >= 2) {
return [trim($parts[0]), trim($parts[1])];
}
return [$table, $table];
}
/**
* 在 from / joins 中找指定表的别名
*/
protected function findTableAlias($baseQuery, string $tableName): ?string
{
[$fromTable, $fromAlias] = $this->parseTableAlias((string)($baseQuery->from ?? ''));
if ($fromTable === $tableName) {
return $fromAlias;
}
foreach (($baseQuery->joins ?? []) as $join) {
[$joinTable, $joinAlias] = $this->parseTableAlias((string)($join->table ?? ''));
if ($joinTable === $tableName) {
return $joinAlias;
}
}
return null;
}
/**
* 把 "1,2,3" / "123" / " 1 , 2 " 转成数组
*/
protected function parseVisibleIds(string $raw): array
{
$raw = trim($raw);
if ($raw === '' || $raw === '*') {
return [];
}
$parts = preg_split('/[,\s]+/u', $raw) ?: [];
$parts = array_filter($parts, static fn($v) => $v !== '');
return array_values(array_unique(array_map('intval', $parts)));
}
}
+2 -3
View File
@@ -1,8 +1,7 @@
<?php
namespace app\model;
namespace plugin\admin\app\model;
use support\Model;
/**
* OpmMwDepartment 模型
@@ -30,7 +29,7 @@ use support\Model;
* @property string $deptId deptId
* @property int $s_report s_report(默认值:1
*/
class OpmMwDepartment extends Model
class OpmMwDepartment extends Base
{
/**
* 数据表名
+3 -2
View File
@@ -1,7 +1,8 @@
<?php
namespace app\model;
namespace plugin\admin\app\model;
use plugin\admin\app\model\Base;
use support\Model;
/**
@@ -38,7 +39,7 @@ use support\Model;
* @property string $url2 url2
* @property int $is_push is_push(默认值:0
*/
class OpmMwHospital extends Model
class OpmMwHospital extends Base
{
/**
* 数据表名
+52 -2
View File
@@ -2,9 +2,59 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<title>医院科室树形结构</title>
<!-- 引入你提供的资源 -->
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body>
iiij
<!-- 树形结构容器 -->
<div class="layui-card">
<div class="layui-card-body">
<div id="hospitalDeptTree" style="padding: 10px;"></div>
</div>
</div>
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script src="/app/admin/admin/js/common.js"></script>
<script>
layui.use(['tree', 'layer', 'jquery'], function(){
var tree = layui.tree;
var layer = layui.layer;
var $ = layui.jquery;
// 渲染医院科室树形结构
function renderHospitalTree() {
// 请求后端接口获取树数据
$.get("/app/admin/test/tree", function(res){
if(res.code === 0){
tree.render({
elem: '#hospitalDeptTree', // 容器ID
data: res.data, // 树数据
showCheckbox: false, // 不显示复选框
onlyIconControl: true, // 仅允许图标展开/折叠
click: function(obj){
// 点击节点事件
layer.msg("你选择了:" + obj.data.title);
console.log("节点数据:", obj.data);
}
});
} else {
layer.msg(res.msg || "加载树形结构失败");
}
}).fail(function(){
layer.msg("接口请求失败,请检查后端服务");
});
}
// 页面加载完成渲染树
$(function(){
renderHospitalTree();
});
});
</script>
</body>
</html>