feat(mcp): MCP 密钥管理后台(CURD 重组 Base/App,密钥仅创建时明文展示)

This commit is contained in:
augushong
2026-08-16 20:55:02 +08:00
parent 15a6f2d841
commit 918d1d4dd5
15 changed files with 593 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
<?php
namespace base\admin\controller\system;
use app\admin\model\SystemAdmin;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnotation;
use app\common\controller\AdminController;
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);
// 行内修改仅允许 statuskey/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();
// 附加创建者用户名:不用 withJoinsystem_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/remarkkey/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();
}
}