21 KiB
xiaoqu-agent-service 设计方案
面向智能体(Kimi Code / Claude 等 MCP 客户端)的业务数据服务:独立新建系统,远程部署,照搬现有 SaaS 的行级数据权限,以"作用域"控制工具可见性,带轻量管理后台。
状态:讨论定稿(2026-09-01),待进入实施。
1. 背景与目标
现有 SaaS(xiaoqu-franchisee)是面向 PC 后台人工操作的系统,接口鉴权(uuid + 短信登录态 + 签名)不适合机器调用。本方案新建一套独立系统,将仓库、客户等业务数据以 MCP 服务形式暴露给智能体,同时提供 CLI 兜底与 Web 管理后台。
设计约束(讨论中明确):
- 与现有系统零代码耦合:不改老系统一行代码,耦合面只有数据库(只读)。
- 照搬现有行级数据权限(
getByIsAgent那套规则),不移植字段级/页面级功能权限。 - 远程部署,MCP over Streamable HTTP。
- 用户熟悉 Java:技术栈定为 JDK 17 + Spring Boot 3 + Vue 3。
2. 总体架构
Kimi Code ──┐
├─ MCP (Streamable HTTP, Bearer ak) ─┐
Claude ─────┤ │
│ ▼
xq CLI ─────┘ (Go 单二进制, 同为 MCP 客户端) ┌─────────────────────────┐
│ xiaoqu-agent-service │
│ (单 jar, Spring Boot 3) │
│ │
┌── Web 管理后台 ──────►│ /mcp MCP 端点 │
│ (Vue3 内嵌打包) │ /api/** 后台 REST │
│ │ static/ 管理页面 │
│ ├─────────────────────────┤
│ │ 鉴权层: ak→人, scope校验 │
│ │ 权限引擎: 行级数据过滤 │
│ │ 数据访问层: 3×MySQL/Redis/ES│
│ └───────┬─────────┬───────┘
│ │ 只读 │ 读写
│ ┌───────▼──┐ ┌───▼────────┐
└────────────────────►│ 现有三库 │ │ 自有库表 │
(user/agent等) │ m0/m1/m2 │ │ token/scope│
│ Redis/ES │ │ tool/audit │
└──────────┘ └────────────┘
关键分层:业务工具只写一遍,MCP 端点(Kimi/Claude/CLI 同为 MCP 客户端)、后台页面共享同一套鉴权、权限引擎与数据访问层。
3. 权限模型(三层)
人员(user) 1──n Token(ak) n──n 作用域(Scope) n──n 工具(Tool)
│
└── 行级数据权限: 跟着"人"走,照搬现有系统
3.1 概念定义
- Tool:MCP 暴露的工具(如
warehouse.stock.query)。代码中用注解声明,启动时同步进tool表。读写属性在注册时标注。 - Scope(作用域):命名的工具集合(如"运营作用域""销售作用域")。纯手工配置:管理页面上创建作用域、勾选工具。新工具上线默认不属于任何作用域 = 任何 token 都调不到,必须管理员显式开通(安全优点)。
- Token(ak):属于某个人,一人多把(Kimi 一把、Claude 一把)。每把 token 勾选任意组合的作用域。可长期有效(
expire_time可空),靠"禁用"回收;可选填过期时间供临时外发场景自毁。 - 职责分离:token/scope 只决定"能用哪些工具";能看哪些行永远由 token 背后的人决定。同一人多把 token,数据视野一致,能力面不同。
3.2 调用判定链
- ak 有效?(SHA-256 哈希匹配、未禁用、未过期)
- 找得到人?(token → user,user 未删除;用户资料优先读 Redis
entity:User:{id}热缓存,变更即时生效,回源 m0user表) - 工具在 token 作用域并集内?→ 否则拒绝(服务端强制,补齐老系统 PermissionInterceptor 空壳的短板)
- 执行查询,按"人"的数据权限规则加行过滤
- 审计落库(人、token、工具、入参摘要、结果、耗时)
注:tools/list 按作用域过滤(2026-09-04 决策,避免"看得到却调不了"的二义性):MCP SDK 工具清单在启动时静态注册,无法按请求过滤,故由 ToolsListScopeFilter 在 servlet 层重写 tools/list 响应。第 3 步的强制点仍是 tools/call——绕过 list 直接调未授权工具,得到的仍是可读的拒绝信息 + denied 审计。
3.3 行级数据权限移植规范(照搬现有系统)
来源:WrapperUtils.getByIsAgent 系列 + authority.isagent。规则:
| 条件 | 行过滤 |
|---|---|
权限组 isagent=0(总部/管理员) |
不过滤,看全部 |
isagent=1(代理商) |
agent_id IN (自己 + 直属下级代理),下级只取一层(agent.supervisor_id) |
| 代管扩展 | 并入 warehouse_agent_auth 中该用户被授权的 manager_agent_id;物业维度再并 agent_manage_district_auth |
物业用户(enterprise_type=2) |
再限 property_id = 自己 |
| 销售部门非负责人(dept 命中字典、post 不在白名单) | 只看 create_id = 自己 |
实现方式:权限引擎输出中性的"可见 agent id 集合 + 过滤字段",由各数据源适配器翻译成 SQL IN 或 ES terms。不做跨库 JOIN——先算 id 集合,再拼到目标库查询(与老系统实际行为一致)。
4. 数据源
| 源 | 内容 | 接入方式 |
|---|---|---|
| m0(权限库) | user、agent、authority、agent_manage_district_auth | 只读连接池 |
| m1(商城库) | 商城业务数据 | 只读连接池(按需启用) |
| m2(业务库) | 仓库/客户/工单等、warehouse_agent_auth | 只读连接池 |
| Redis | entity:User:{id} 用户热缓存、Dictionary 字典 |
只读 |
| ES | 部分列表查询 | 预留,一期可后置 |
- 全部使用只读数据库账号(物理封死误写);将来写工具单独配有写权限的账号。
- 配置按环境分文件(dev/test/prd);dev 环境密码明文写在
application-dev.yml(内网 dev 库,与现有 pom.xml dev profile 明文同一惯例),生产/测试环境不得在配置文件写明文密码。
4.1 Entity 生成
不依赖 xiaoqu-publicbeanandmap jar(MyBatis-Plus 3.4 旧依赖链与 Boot 3 冲突)。新工程内置 mybatis-plus-generator(run-once),表白名单制生成 Entity/Mapper,按来源库分包 entity/m0|m1|m2,生成物提交进仓库归新系统所有。Mapper XML 基本不用,LambdaQueryWrapper 为主。
5. 对外形态
5.1 MCP(主形态)
- Streamable HTTP 传输,端点
https://mcp.<域名>/mcp。 - 客户端配置(Kimi Code
~/.kimi-code/mcp.json或项目级.kimi-code/mcp.json,ak 明文直写;项目级文件已加入.gitignore):
{
"mcpServers": {
"xiaoqu": {
"url": "https://mcp.<域名>/mcp",
"headers": {
"Authorization": "Bearer <ak>"
}
}
}
}
- Claude Code:
claude mcp add --transport http xiaoqu https://mcp.<域名>/mcp --header "Authorization: Bearer <ak>"。 - ak 明文只出现在服务端签发响应和运行时;服务端保存 SHA-256 哈希及加密密文,含 ak 的客户端配置文件不得提交版本库。
5.2 Web 管理后台(四个页面)
- 用户与 Token 管理:人员选自现有
user表,只列企业账号(enterprise_type0=企业内部 / 1=代理,历史 NULL 保留),物业账号(=2)不进列表——Token 是给能查代理商/客户数据的人用的;每人名下签发/禁用 token,展示 key 前缀、作用域、最后使用时间;选中人员展示其数据权限归属(权限组/isagent、所属代理、企业类型、可见范围说明,只读,便于排查"查不到数据")。一作用域一 Token:签发时选 N 个作用域生成 N 把 token(每把独立绑定单作用域,独立吊销/审计,明文逐把展示一次);编辑存量 token 时作用域收敛为单选。 - 工具管理:工具列表来自服务端注册自动同步(工具名/分类/读写类型页面只读);展示每个工具被哪些作用域引用;「停用」为全局紧急开关。分类(category)在代码注册时由开发者声明(工具的业务域归属是客观属性,不允许页面上修改,避免与代码漂移)。
- 作用域管理:作用域 CRUD + 勾选工具(工具按 category 分组展示)。
- 审计日志:按人/token/工具/时间筛选。
原型见 doc/prototype/admin-prototype.html(浏览器直接打开)。
后台登录为独立的管理员账号密码(session/JWT),与 ak 体系分离。
5.3 CLI(兜底,P3 之后实施)
核心决策:CLI 不是第二套服务,而是 /mcp 端点的一个 MCP 客户端(Streamable HTTP + tools/call)。服务端零新增暴露面,ak/作用域/数据权限/审计自动生效;CLI 启动时通过 tools/list 自发现工具,服务端新增工具无需升级 CLI。
- 语言:Go(与服务端 Java 栈解耦,语言自由)。单文件静态二进制分发,交叉编译覆盖 Mac/Linux,调用环境无需任何运行时。备选 Python(官方 MCP SDK 最全,但依赖本机 Python/uv)。
- 命令规范:从工具名派生,不手工维护——
warehouse.stock.query→xq warehouse stock query --warehouse-id 7;参数与 help 从tools/list的 schema 生成。 - 输出约定:stdout 恒为 JSON(
--pretty给人看);错误统一{"error":{code,message}}且 exit code 非 0;--schema吐入参 JSON Schema 供 agent 自发现。 - 探测命令:
xq whoami(ak 归属的人 + 作用域,兼作连通性自检)、xq tools(列出本 ak 可用工具)。 - 配置:
~/.xq/config.yaml存 base-url、多环境 profile(--env dev|test|prd)与 ak 明文(本机文件,权限 600),与 mcp.json 明文直写同一风格。 - 服务端配套改动(仅两点):审计表加
client_type(mcp-kimi / mcp-claude / cli,从请求头识别);错误消息面向"人和 agent 都可读"(如权限拒绝时提示应由管理员在哪个作用域开通)。
5.4 Open API(服务间接口)
供其他业务系统/智能体平台在服务端到服务端场景下获取用户 Token,路径 /api/open/**,与 ak 体系、后台 session 体系完全分离。
- 鉴权:静态服务间密钥,header
X-Open-Key,配置项app.open.api-key(dev 明文写application-dev.yml;配置为空 = 接口关闭)。常量时间比较防时序侧信道。 POST /api/open/token/ensure(get-or-create):- 入参:
mobile、email(至少一个,手机号优先)、scopeCode(作用域编码,必填)。 - 人员只认企业账号(
enterprise_type0/1,NULL 保留,与后台用户列表同口径);查不到 → 404。
- 入参:
- 该人员在该作用域下已存在有效 Token(正常且未过期)→
created=false,只回keyPrefix/expireTime等元信息,明文不回传。- 不存在 → 自动创建永久 Token(一作用域一 Token,memo 标记
open-api 自动签发),created=true返回明文,仅此一次。 - 全程写
audit_log(tool_name=open.token.ensure,client_type=open-api,参数摘要脱敏)。
- 不存在 → 自动创建永久 Token(一作用域一 Token,memo 标记
6. 技术栈与工程结构
- JDK 17 + Spring Boot 3.x(Spring AI MCP starter 的硬性要求)
- MCP:
spring-ai-starter-mcp-server-webmvc,protocol: STREAMABLE;工具@McpTool注解声明 - 数据:MyBatis-Plus 3.5(
mybatis-plus-spring-boot3-starter)三数据源、Redis、ES 客户端 - 前端:Vue 3 + Element Plus,
yarn build产物打入 jarstatic/(frontend-maven-plugin联动构建),单 jar 部署,Nginx 终结 TLS - ⚠️ 开工先验证 Spring Boot / Spring AI 版本组合(Spring Boot 3.5.x 曾报 streamable NPE:spring-projects/spring-ai#6274),跑通 demo 再铺开
xiaoqu-agent-service/
├── src/main/java/me/iiv/agent/
│ ├── config/ # 三数据源、Redis、ES、MCP server
│ ├── mcp/ # @McpTool 工具(按业务域分包) + 注册同步
│ ├── security/ # ak Filter、身份上下文、scope 校验
│ ├── permission/ # 行级数据权限引擎(移植 getByIsAgent)
│ ├── admin/ # 后台 REST: 用户/token、作用域、审计
│ ├── entity/ # m0/m1/m2 生成实体
│ └── common/ # 审计 AOP、限流、结果包装
├── src/main/resources/ # application-{dev,test,prd}.yml
├── codegen/ # mybatis-plus-generator 配置(run-once)
└── front/ # Vue3 + Element Plus
7. 自有库表(新 schema,如 xiaoqu_agent_svc)
| 表 | 关键字段 | 说明 |
|---|---|---|
agent_api_key |
user_id、key_prefix、key_hash(SHA-256)、status、expire_time(可空=永久)、last_used_time | token;user_id 逻辑关联 m0.user |
scope |
name、code、memo、status | 作用域 |
scope_tool |
scope_id、tool_id | 作用域-工具 |
token_scope |
token_id、scope_id | token-作用域 |
tool |
name、category、description、kind(read/write)、status | 工具注册表,启动时同步;category 代码声明 |
audit_log |
user_id、token_id、tool_name、params_digest、result_status、cost_ms、client_ip、client_type | 审计;client_type 区分 mcp-kimi/mcp-claude/cli |
admin_user |
account、password_hash | 后台管理员 |
均带标准审计五件套(create/modify id/time、soft_delete)。
8. 安全基线
- dev/内网测试阶段允许裸跑 HTTP(客户端 URL 用
http://);对外/生产必须 HTTPS(Nginx 终结 TLS,ak 在 HTTP 下明文可被抓包) - ak 只存哈希;审计不记录 ak;限流按 token 维度(防 agent 死循环打爆库)
- 数据访问全部只读账号;scope 最小化发放,新工具默认不可用
- 可选:Nginx IP 白名单(客户端出口固定时)
9. 阶段规划
| 阶段 | 内容 | 验收 |
|---|---|---|
| P1 骨架 | 工程初始化、版本组合验证、三数据源连通、codegen 跑通 | demo 工具经 MCP 调通 |
| P2 权限 | ak 鉴权 Filter、scope 校验、数据权限引擎、审计 | 不同权限组的人查询结果隔离正确 |
| P3 首批工具 | 代理商查询 + 客户(物业)查询(定义见 §9.1)+ tool 表同步 | Kimi/Claude 双端实测 |
| P4 管理后台 | 用户/token、工具、作用域、审计四页面(Vue3 内嵌) | 管理员全流程自助配置 |
| P5 加固 | 限流、ES 接入(按需)、写工具评审开放、CLI(Go 单二进制) | 生产上线 |
9.1 首期工具定义(P3,均已确认)
对应现有接口 /agent/listByPage 与 /property/listByPage,数据均查 m0 库,行级权限照搬 §3.3。入参精选(不做 beanToWrapper 全字段放行),枚举值写入工具 description 供模型使用;名称富化(supervisorName/agentName/createName)批量查询,消除老实现的 N+1。
agent.list.query(分类 customer,读)
- 语义照搬现有页面:isagent=1 时只查下级代理(
supervisor_id = 自己agentId);isagent=0 看全部。额外允许入参传id精确查询(可查到自身)。 - 入参:
id(精确)、name(模糊)、level(0总部/1一级/2二级)、channelNo(Z/F/D)、province、city、serialnumber、page(默认1)、rows(默认20,上限100)。不含type(已确认去掉)。 - 出参:id、name、level、channelNo、serialnumber、supervisorName、province/city/countyseat、contact、phone、agencyarea、memo、createTime。
- 已裁剪:开票信息、银行账户、营业执照图片 URL(OSS 依赖,二期再议)。
property.list.query(分类 customer,读)
- 语义照搬:完整版
getByIsAgent(下级代理 + warehouse_agent_auth 代管 + agent_manage_district_auth 代管物业 + 销售非负责人规则)。销售非负责人规则 2026-09-03 决策:1:1 复刻老代码 saleFlag 分支(WrapperUtils.java:716-735)——可见范围 = 自己创建的物业(create_id=自己)∪ 代管授权非空时 agent_id IN (下级+代管,移除自己) 的物业,即id IN (...)结构,且与其余规则互斥(saleFlag 命中即 return);不再按 §3.3 表格收窄为 AND create_id=自己。不含 enterprise_type=2(物业用户限自己物业)限制——物业用户不查此列表,与老接口/property/listByPage(PropertyController.java:95)语义一致(2026-09-03 决策;§3.3 通用规则表保留该条,仅此处不适用)。 - 入参:
name(模糊)、agentId、serialnumber、province、city、contact、phone、page、rows。 - 出参:id、name、serialnumber、agentName、createName(销售)、province/city/countyseat、address、contact、phone、createTime。
- 已裁剪:开票信息、银行账户。
10. 已明确的取舍记录
- 不移植字段级(page_field 六表)功能权限 → 用 scope 替代,更简单且服务端强制
- 作用域纯手工配置,不做代码内默认归属 → 新工具默认关闭
- 工具分类(category)代码注册时声明,页面只读 → 分类与代码业务归属不漂移
- token 可长期有效 → 靠禁用回收,过期时间可选
- CLI 复用
/mcp端点做 MCP 客户端,不开第二套服务 → 权限逻辑只有一份;Go 单二进制分发,与服务端 Java 栈解耦 - 不依赖老 jar,Entity 自行生成 → 与老系统零代码依赖
- 前后端一体打包单 jar → 可逆决定,将来可拆
- 一作用域一 Token(2026-09-04)→ 收回某能力不需整把吊销;数据模型不变(token_scope 每把只写一行)
- 长期 Token 仅按用户首次创建返回明文一次;后续复用元信息,不对外提供二次获取明文接口(服务端仅用于禁用/审计与治理)
- Open API 鉴权用配置文件静态密钥(dev)→ 服务间调用方少,落库+轮换留给生产阶段
- 列表类接口一律服务端分页,pageSize/rows 上限 100(超出截断为 100)→ 防止一次性拉全表,MCP 工具与后台 API 同一尺度
11. 开发环境(dev)
本机即开发环境并兼任部署机:程序直接跑在本机,dev 阶段裸跑 HTTP(§8),端口避开老系统的 8091。数据库/Redis/ES 连接信息沿用 xiaoqu-franchisee/pom.xml dev profile:
| 组件 | 地址 | 账号 | 库/DB |
|---|---|---|---|
| MySQL m0(权限库) | 192.168.1.181:3306 | root | xiaoqu_comples_d(user/agent/authority/代管物业表在此) |
| MySQL m1(商城库) | 192.168.1.181:3306 | root | xiaoqu_mall_d(按需启用) |
| MySQL m2(业务库) | 192.168.1.181:3306 | root | xiaoqu_franchisee_d(warehouse_agent_auth 在此) |
| Redis | 192.168.1.181:6379 | 密码见 pom dev | db5(entity:User:{id}、Dictionary)、db13 |
| ES | 192.168.1.152:9200 | 无认证 | 预留,一期不用 |
- 密码等敏感值不入库本文档;dev 环境 DB/Redis 连接信息明文写在新系统
application-dev.yml(内网 dev 库,与pom.xmldev profile 明文同一惯例)。 - 自有库表(§7)建在同实例新 schema:dev 用
xiaoqu_agent_svc_d(沿用_d后缀约定),首次部署前手工建库。 - 新系统与老的 dev 后端(Jetty 8091)可同时运行,互不干扰——注意两边连的是同一套 dev 数据库和 Redis,老系统dev库的数据变动会同时影响两边的查询结果。
12. 开发流程约定
- 一律用 git worktree 开发:每个需求从 main 拉分支到
../oldproject-worktrees/<feature>/,在 worktree 里改代码、跑测试;验证通过后合并回 main 再推送远程。主检出(本目录)保持可随时部署的干净状态。 - 合并回 main 后用
xiaoqu-agent-service/deploy/deploy.sh restart完成构建+重启,接口实测通过再git push。 - 后台列表页统一服务端分页(page 从 1 开始,pageSize 默认 20、上限 100);MCP 工具
rows同样上限 100,超出截断并在notice字段提示。 - 新增/修改 MCP 工具必须同步更新
doc/agent-mcp-tools-reference.md(入参、出参、权限语义、示例),工具对外契约以该文档为准。