mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-31 05:05:33 +08:00
262 lines
9.7 KiB
PHP
262 lines
9.7 KiB
PHP
<?php
|
||
|
||
namespace base\admin\controller\system;
|
||
|
||
use app\admin\model\SystemAdmin;
|
||
use app\admin\model\SystemMcpKeyNode;
|
||
use app\admin\service\annotation\ControllerAnnotation;
|
||
use app\admin\service\annotation\NodeAnotation;
|
||
use app\admin\service\NodeService;
|
||
use app\common\controller\AdminController;
|
||
use app\common\service\AuthService;
|
||
use think\App;
|
||
|
||
/**
|
||
* Class McpKeyBase.
|
||
* @ControllerAnnotation(title="MCP密钥管理")
|
||
*/
|
||
class McpKeyBase extends AdminController
|
||
{
|
||
use \app\admin\traits\Curd;
|
||
|
||
protected $sort = [
|
||
'id' => 'desc',
|
||
];
|
||
|
||
public function __construct(App $app)
|
||
{
|
||
parent::__construct($app);
|
||
|
||
$this->model = new \app\admin\model\SystemMcpKey();
|
||
|
||
$this->assign('select_list_status', $this->model::SELECT_LIST_STATUS, true);
|
||
|
||
// 行内修改仅允许 status;key/key_prefix/bind_admin_id 为敏感字段,一律排除
|
||
$this->allowModifyFields = [
|
||
'status',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @NodeAnotation(title="列表")
|
||
*/
|
||
public function index()
|
||
{
|
||
if ($this->request->isAjax()) {
|
||
if (input('selectFields')) {
|
||
return $this->selectList();
|
||
}
|
||
list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames();
|
||
$count = $this->model
|
||
->where($where)
|
||
->group($group)
|
||
->count();
|
||
$list = $this->model
|
||
->where($where)
|
||
->page($page, $limit)
|
||
->order($this->sort)
|
||
->group($group)
|
||
->select();
|
||
|
||
// 附加创建者用户名:不用 withJoin(system_admin 与主表有 id/status 等同名字段会歧义),
|
||
// 一次 IN 查询建立 id => username 映射后回填,避免 N+1。
|
||
$adminIds = [];
|
||
foreach ($list as $vo) {
|
||
!empty($vo->bind_admin_id) && $adminIds[] = $vo->bind_admin_id;
|
||
}
|
||
$adminNames = empty($adminIds) ? [] : SystemAdmin::whereIn('id', array_unique($adminIds))->column('username', 'id');
|
||
foreach ($list as $vo) {
|
||
$vo->bind_admin_username = $adminNames[$vo->bind_admin_id] ?? '';
|
||
}
|
||
|
||
$data = [
|
||
'code' => 0,
|
||
'msg' => '',
|
||
'count' => $count,
|
||
'data' => $list,
|
||
];
|
||
|
||
return json($data);
|
||
}
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* @NodeAnotation(title="添加")
|
||
*/
|
||
public function add()
|
||
{
|
||
if ($this->request->isPost()) {
|
||
$post = $this->request->post();
|
||
// 表单仅提交 title/status/remark;密钥三要素与统计字段一律服务端生成,忽略客户端传入
|
||
unset($post['id'], $post['key'], $post['key_prefix'], $post['bind_admin_id'], $post['use_num'], $post['last_use_time']);
|
||
$rule = [
|
||
'title|密钥名称' => 'require|max:50',
|
||
];
|
||
$this->validate($post, $rule);
|
||
|
||
// 明文密钥仅此一次出现在内存与响应中:不落库、不写日志
|
||
$secretKey = 'sk-mcp-' . bin2hex(random_bytes(24));
|
||
$post['key'] = hash('sha256', $secretKey);
|
||
$post['key_prefix'] = substr($secretKey, 0, 16);
|
||
$post['bind_admin_id'] = $this->getAdminId();
|
||
try {
|
||
$save = $this->model->save($post);
|
||
} catch (\Exception $e) {
|
||
$this->error('保存失败:' . $e->getMessage());
|
||
}
|
||
if ($save) {
|
||
// data 携带明文密钥,前端弹层展示一次后即丢弃
|
||
$this->success('保存成功', ['secret_key' => $secretKey]);
|
||
}
|
||
$this->error('保存失败');
|
||
}
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* @NodeAnotation(title="编辑")
|
||
*/
|
||
public function edit($id)
|
||
{
|
||
$row = $this->model->find($id);
|
||
empty($row) && $this->error('数据不存在');
|
||
if ($this->request->isPost()) {
|
||
$post = $this->request->post();
|
||
// 白名单:仅允许改 title/status/remark;key/key_prefix/bind_admin_id 及统计字段不可改
|
||
$post = array_intersect_key($post, array_flip(['title', 'status', 'remark']));
|
||
$rule = [
|
||
'title|密钥名称' => 'require|max:50',
|
||
];
|
||
$this->validate($post, $rule);
|
||
try {
|
||
$save = $row->save($post);
|
||
} catch (\Exception $e) {
|
||
$this->error('保存失败:' . $e->getMessage());
|
||
}
|
||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||
}
|
||
$this->assign('row', $row);
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 授权页:勾选创建者当前权限的子集给密钥.
|
||
* @NodeAnotation(title="授权")
|
||
*/
|
||
public function authorize($id)
|
||
{
|
||
$row = $this->model->find($id);
|
||
empty($row) && $this->error('数据不存在');
|
||
|
||
// 全量动作节点来自 getNodeTree 展开全部 type=2 节点
|
||
$nodeService = new NodeService();
|
||
$module_list = $nodeService->getNodeTree();
|
||
|
||
// 创建者当前节点集合:与 checkNode 完全一致的判定链(含超管直通/auth_on/动态黑名单)
|
||
$authService = new AuthService($row->bind_admin_id);
|
||
$allActionNodes = [];
|
||
foreach ($module_list as $module) {
|
||
foreach ($module['children'] as $controller) {
|
||
foreach ($controller['children'] as $action) {
|
||
$allActionNodes[] = $action['node'];
|
||
}
|
||
}
|
||
}
|
||
$creatorAllowedNodes = $authService->getAdminAllowedNodes($allActionNodes, $row->bind_admin_id);
|
||
|
||
// 已选节点 = 库中记录 ∩ 创建者当前集合(防漂移:创建者权限被回收后旧勾选自动失效)
|
||
$checkedNodeList = (new SystemMcpKeyNode())
|
||
->where('key_id', $id)
|
||
->column('node');
|
||
$checkedNodeList = array_values(array_intersect($checkedNodeList, $creatorAllowedNodes));
|
||
|
||
// 树过滤:动作节点不在创建者集合内整级剔除;无动作节点的控制器、无控制器的模块不显示
|
||
foreach ($module_list as $module_key => $module) {
|
||
foreach ($module['children'] as $controllerKey => $controller) {
|
||
foreach ($controller['children'] as $actionKey => $action) {
|
||
if (!in_array($action['node'], $creatorAllowedNodes)) {
|
||
unset($module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]);
|
||
continue;
|
||
}
|
||
$checked = in_array($action['node'], $checkedNodeList);
|
||
$checked_string = $checked ? 'checked' : '';
|
||
$disabled_string = '';
|
||
if (!$action['auth']) {
|
||
// 免鉴权节点创建者恒有权限,照 auth/authorize 惯例固定勾选并禁用
|
||
$checked_string = 'checked';
|
||
$disabled_string = 'disabled';
|
||
}
|
||
$module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]['checked'] = $checked_string;
|
||
$module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]['disabled'] = $disabled_string;
|
||
}
|
||
if (empty($module_list[$module_key]['children'][$controllerKey]['children'])) {
|
||
unset($module_list[$module_key]['children'][$controllerKey]);
|
||
}
|
||
}
|
||
if (empty($module_list[$module_key]['children'])) {
|
||
unset($module_list[$module_key]);
|
||
}
|
||
}
|
||
|
||
$this->assign('row', $row);
|
||
$this->assign('checked_node_list', $checkedNodeList);
|
||
$this->assign('module_list', array_values($module_list));
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 授权保存:先删后插,服务端强校验防越权.
|
||
* @NodeAnotation(title="授权保存")
|
||
*/
|
||
public function saveAuthorize()
|
||
{
|
||
$this->checkPostRequest();
|
||
$id = $this->request->post('id');
|
||
$node = (array) $this->request->post('node', []);
|
||
|
||
$row = $this->model->find($id);
|
||
empty($row) && $this->error('数据不存在');
|
||
|
||
// 服务端强校验:每个提交节点必须 ∈ 创建者当前节点集合(重新计算,防权限已变)
|
||
$nodeService = new NodeService();
|
||
$allActionNodes = [];
|
||
foreach ($nodeService->getNodeTree() as $module) {
|
||
foreach ($module['children'] as $controller) {
|
||
foreach ($controller['children'] as $action) {
|
||
$allActionNodes[] = $action['node'];
|
||
}
|
||
}
|
||
}
|
||
$authService = new AuthService($row->bind_admin_id);
|
||
$creatorAllowedNodes = $authService->getAdminAllowedNodes($allActionNodes, $row->bind_admin_id);
|
||
foreach ($node as $vo) {
|
||
if (!in_array($authService->parseNodeStr($vo), $creatorAllowedNodes)) {
|
||
$this->error('包含无权限节点');
|
||
}
|
||
}
|
||
|
||
try {
|
||
$keyNode = new SystemMcpKeyNode();
|
||
$keyNode->where('key_id', $id)->delete();
|
||
if (!empty($node)) {
|
||
$saveAll = [];
|
||
foreach ($node as $vo) {
|
||
$saveAll[] = [
|
||
'key_id' => $id,
|
||
'node' => $authService->parseNodeStr($vo),
|
||
];
|
||
}
|
||
$keyNode->saveAll($saveAll);
|
||
}
|
||
} catch (\Exception $e) {
|
||
$this->error('保存失败');
|
||
}
|
||
$this->success('保存成功');
|
||
}
|
||
}
|