269 lines
21 KiB
Markdown
269 lines
21 KiB
Markdown
# 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 调用判定链
|
||
|
||
1. ak 有效?(SHA-256 哈希匹配、未禁用、未过期)
|
||
2. 找得到人?(token → user,user 未删除;用户资料优先读 Redis `entity:User:{id}` 热缓存,变更即时生效,回源 m0 `user` 表)
|
||
3. 工具在 token 作用域并集内?→ 否则拒绝(服务端强制,补齐老系统 PermissionInterceptor 空壳的短板)
|
||
4. 执行查询,按"人"的数据权限规则加行过滤
|
||
5. 审计落库(人、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`):
|
||
|
||
```json
|
||
{
|
||
"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_type` 0=企业内部 / 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_type` 0/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`,参数摘要脱敏)。
|
||
|
||
## 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` 产物打入 jar `static/`(`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.xml` dev 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`**(入参、出参、权限语义、示例),工具对外契约以该文档为准。
|