i更新
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# AI 助手(MCP)只读数据查询:实现与部署
|
||||
|
||||
日期:2026-09-24。状态:只读查询与业绩工具已上线;AI 后台浏览器(第 3 节末、第 5 节第 7 步)已在本地一次性测试库完成实现与测试,待同步上线并执行 `2026_09_24_ai_mcp_console.sql`。
|
||||
日期:2026-09-24。状态:只读查询、业绩工具与 AI 后台浏览器已上线;物流查询工具(第 3 节末、第 5 节第 8 步)已在本地一次性测试库完成实现与测试,待同步上线(不涉及数据库)。
|
||||
|
||||
对接方:行知 AI 工作助手(方案见行知项目 `docs/zyt-mcp-plan.md`)。员工在行知里用甄养堂后台账号密码绑定,之后 AI 按该账号自己的权限和数据范围只读查询甄养堂数据。
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
- 端点:`POST https://admin.zhenyangtang.com.cn/mcp`,Streamable HTTP,只返回 JSON,无会话;支持协议 2025-11-25 / 2025-06-18 / 2025-03-26;`GET` 返回 405。
|
||||
- 请求头:`Authorization: Bearer zyt_ai_…`(必需)、`MCP-Protocol-Version`、`X-Xingzhi-Task-Id`(可选,写入审计)。浏览器 `Origin` 不在白名单一律 403。
|
||||
- 工具(全部标注 `readOnlyHint`):`zyt_whoami`、`zyt_catalog`、`zyt_describe`、`zyt_query`、`zyt_get`、`zyt_count`、`zyt_file`,以及按权限出现的快捷统计工具 `zyt_stats_appointments`、`zyt_stats_doctor_workload`、`zyt_stats_orders`、`zyt_stats_prescription_orders`、`zyt_my_patients`、`zyt_roster`,和业绩工具 `zyt_perf_assistants`、`zyt_perf_doctors`、`zyt_stats_performance`、`zyt_perf_trend`(见下)。
|
||||
- 工具(全部标注 `readOnlyHint`):`zyt_whoami`、`zyt_catalog`、`zyt_describe`、`zyt_query`、`zyt_get`、`zyt_count`、`zyt_file`,以及按权限出现的快捷统计工具 `zyt_stats_appointments`、`zyt_stats_doctor_workload`、`zyt_stats_orders`、`zyt_stats_prescription_orders`、`zyt_my_patients`、`zyt_roster`,和业绩工具 `zyt_perf_assistants`、`zyt_perf_doctors`、`zyt_stats_performance`、`zyt_perf_trend`(见下),物流工具 `zyt_logistics_order`、`zyt_logistics_patient`(见第 3 节末)。每个工具带中文显示名(MCP `title` / `annotations.title`,如“医生业绩排行”“订单物流查询”),行知的执行步骤显示它而不是工具标识。
|
||||
|
||||
### 业绩工具(2026-09-24 增补,`service/PerfTools.php`)
|
||||
|
||||
@@ -63,6 +63,21 @@
|
||||
- 同时修复:`params` 里写的 `page`/`page_size` 自动当作分页;限流与每日行数计数在缓存读写出错时放行并记日志(文件缓存下并发调用可能读到写了一半的文件,之前会让整次查询变成“内部错误”);内部错误提示带上异常类型,便于对照服务器日志。
|
||||
- 授权接口:`POST /mcp/auth/grant`、`POST /mcp/auth/revoke`(Bearer)、`GET /mcp/auth/whoami`(Bearer),返回与后台一致的 `{code, show, msg, data}`;失败时 `data.reason` 为 `invalid_credentials / disabled / need_change_password / need_bind_wecom / no_ai_permission / locked / feature_disabled / ip_not_allowed / invalid_request`。
|
||||
|
||||
### 物流查询(2026-09-24 增补,`service/LogisticsTools.php`)
|
||||
|
||||
原来的“物流轨迹”接口(`tcm.prescriptionOrder/logisticsTrace`)一直待整改:库里没有轨迹时会调快递100,逐单校验对任一订单权限直接放行。现在改为两个专用工具,AI 可以直接回答“某个订单/某位患者的药到哪了”:
|
||||
|
||||
| 工具 | 查询方式 | 返回 |
|
||||
|---|---|---|
|
||||
| `zyt_logistics_order` 订单物流查询 | 业务订单号(PO…)或快递单号 | 快递公司、运单号、订单履约状态、物流状态、是否签收与签收时间、轨迹(最新在前,默认 30 条)、数据更新时间与来源、快递公司官网查询页 |
|
||||
| `zyt_logistics_patient` 患者物流查询 | 患者姓名/手机号或患者ID,可加创建日期范围 | 该患者最近订单(默认 10 单)各自的快递、状态和最新动态;只匹配到一位患者时附最近一单已发货订单的完整轨迹;同名患者分开列出并提示确认 |
|
||||
|
||||
- 找订单:只通过「处方业务订单」列表(以调用账号身份执行后台原列表),能查到的订单与后台列表完全一致(医助默认只看本人创建的订单,再叠加数据范围);不使用逐单接口的 canAccessOrder。患者姓名、手机号取订单所属诊单。
|
||||
- 看轨迹:还需要后台「物流轨迹」权限(`tcm.prescriptionOrder/logisticsTrace`),没有时只给快递公司、单号和订单状态。
|
||||
- 数据来源:优先读系统已同步的轨迹(`zyt_express_tracking` / `zyt_express_trace`,由 `express:auto-update`、`gancao:sync-logistics` 等同步任务和甘草、洛阳药房回调写入),在只读事务里查询,并写明这张运单最后一次同步的时间和来源。同步任务需要在服务器 crontab 中运行(代码里只有建议周期);订单已完成/取消或运单已签收后不再更新。
|
||||
- 实时查询:系统里还没有这张运单的轨迹时,默认按需实时查询(参数 `live=false` 可关闭):与后台打开订单详情时自动查询的是同一路径(`PrescriptionOrderLogic::logisticsTrace` → 快递100 / 京东官方接口,不写库),每个账号每小时最多 `LOGISTICS_LIVE_PER_HOUR` 次(默认 30),`.env [AI_MCP] LOGISTICS_LIVE = false` 可整体关闭。患者查询只对最近一单做实时查询,列表里的各单只看已同步的最新动态。
|
||||
- 脱敏:收件人电话、患者电话按权限脱敏;只给省市区,不给详细地址;轨迹文字里的手机号(如快递员电话)同样脱敏。审计记录返回的订单ID和行知任务号。
|
||||
|
||||
### AI 后台浏览器(2026-09-24 增补,`controller/ConsoleController.php`、`service/ConsoleService.php`)
|
||||
|
||||
MCP 工具只能查数据目录里开放的资源。为了让 AI 也能看后台网页上才有的内容,并在成员逐次批准下代为操作,行知在自己的服务器上运行一个内置浏览器(无界面 Edge),用成员已绑定的 AI 授权免密码登录本后台。本模块只负责换取和收回这个登录:
|
||||
@@ -95,6 +110,8 @@ LOCK_MINUTES = 30
|
||||
REQUIRE_PASSWORD_CHANGED = true
|
||||
CONSOLE_ENABLED = true ; AI 后台浏览器总开关(另需角色勾选 ai.mcp/console);紧急停用设为 false
|
||||
CONSOLE_TTL_MINUTES = 120 ; AI 浏览器后台会话有效期(10–480 分钟)
|
||||
LOGISTICS_LIVE = true ; 物流查询:系统里没有轨迹时是否实时查询快递公司(快递100 按次计费)
|
||||
LOGISTICS_LIVE_PER_HOUR = 30 ; 每个账号每小时最多实时查询几次
|
||||
```
|
||||
|
||||
限流和锁定使用系统缓存;线上建议 `cache.driver = redis`(文件缓存下计数为近似值)。服务在负载均衡或 CDN 之后时,需先让 `request()->ip()` 取到真实客户端 IP,`ALLOWED_IPS` 才有意义。
|
||||
@@ -108,6 +125,7 @@ CONSOLE_TTL_MINUTES = 120 ; AI 浏览器后台会话有效期(10–480 分钟
|
||||
5. `.env` 设置 `[AI_MCP] ENABLED = true` 与 `ALLOWED_IPS`;nginx 对 `/mcp` 与 `/mcp/auth/grant` 加 `limit_req`,并确认不缓冲响应。
|
||||
6. 在行知管理员页面配置组织连接器:MCP 地址 `https://admin.zhenyangtang.com.cn/mcp`,授权/撤销/身份接口为同域的 `/mcp/auth/grant|revoke|whoami`,工具名前缀关闭,只读工具自动放行。
|
||||
7. **AI 后台浏览器(同日增补)**:同步 `server/app/mcp/`(新增 `controller/ConsoleController.php`、`service/ConsoleService.php`,改动 `service/{GrantService,McpConfig}.php`);执行 `server/database/migrations/2026_09_24_ai_mcp_console.sql`(只新增一个按钮权限点,可重复执行);在“权限管理 > 角色”给需要的角色勾选“允许 AI 使用后台浏览器”(`ai.mcp/console`)。行知侧已配置好组织连接器的“管理后台”:地址 `https://admin.zhenyangtang.com.cn/admin/`,免登录接口 `/mcp/console/open`,退出接口 `/mcp/console/close`,修改逐次审批。
|
||||
8. **物流查询(同日增补)**:同步 `server/app/mcp/`(新增 `service/LogisticsTools.php`,改动 `service/{Tools,McpConfig,Protocol}.php`、`catalog/review/tcm.php`)与 `server/tests/AiMcpLogisticsTest.php`,不涉及数据库。需要看轨迹的角色保持勾选后台「物流轨迹」权限;确认服务器 crontab 在运行快递轨迹同步任务(`php think express:auto-update` 约每 10 分钟、`php think gancao:sync-logistics` 约每 30 分钟),否则只能靠实时查询。
|
||||
|
||||
新增后台接口或页面后:运行 `php app/mcp/cli/catalog.php --write` 重新盘点,并在 `review/` 给新资源写结论;`php server/tests/AiMcpUnitTest.php` 会报告尚未审核的只读接口数量。
|
||||
|
||||
@@ -120,6 +138,7 @@ AI_MCP_TEST_MYSQL=1 php server/tests/AiMcpReadOnlyTest.php
|
||||
AI_MCP_TEST_MYSQL=1 AI_MCP_TEST_BASE_URL=http://127.0.0.1:8099 php server/tests/AiMcpHttpContractTest.php
|
||||
AI_MCP_TEST_MYSQL=1 AI_MCP_TEST_BASE_URL=http://127.0.0.1:8099 php server/tests/AiMcpPerfTest.php # 业绩工具:口径、排名、数据范围、走势、图表、权限回退
|
||||
AI_MCP_TEST_MYSQL=1 AI_MCP_TEST_BASE_URL=http://127.0.0.1:8099 php server/tests/AiMcpConsoleTest.php # AI 后台浏览器(需先执行 2026_09_24_ai_mcp_console.sql)
|
||||
AI_MCP_TEST_MYSQL=1 AI_MCP_TEST_BASE_URL=http://127.0.0.1:8099 php server/tests/AiMcpLogisticsTest.php # 物流查询(运行实例须设 PHP_LOGISTICS_KUAIDI100_DISABLE=true PHP_JD_LOGISTICS_DISABLE=true,避免外呼)
|
||||
php app/mcp/cli/probe.php --admin=<ID> [--only=tcm.] # 默认不执行不开放的、会调外部接口的资源
|
||||
php app/mcp/cli/coverage.php
|
||||
```
|
||||
@@ -130,7 +149,9 @@ php app/mcp/cli/coverage.php
|
||||
|
||||
业绩工具(同日增补):`AiMcpPerfTest` 通过(医助诊金计入部分退款单、医生成交金额不计;取消单都不计;经理看两名下属医助/医生、医助只看自己;部门名称解析;按日/周/月走势;图表输出;排行榜权限点停用时回退到 Tab 权限);原有三个测试与探测脚本无回归;行知端到端(真实行知后端 + MCP 客户端 + 本模块)确认模型拿到 4 个业绩工具、图表代码块完整到达并存入回答。上线只需同步 `server/app/mcp/`(新增 `service/PerfTools.php`,改动 `service/{Tools,Catalog,Dispatcher,RateLimiter,Protocol}.php`、`catalog/review/{stats.php,README.md}`),不涉及数据库。第二次修正(指标命名、单人对账、统计明细翻页)只改 `service/{PerfTools,Tools,Protocol}.php` 与 `tests/AiMcpPerfTest.php`,四个测试全部通过。
|
||||
|
||||
AI 后台浏览器(同日增补):`AiMcpConsoleTest` 通过——无效授权 401;没有 `ai.mcp/console` 被拒且不建会话;签发 terminal=8 会话、有效期 2 小时,关闭“多处登录”时电脑端登录不受影响;换来的令牌能直接访问后台接口;再次打开沿用同一会话;关闭后令牌立即失效、重复关闭无害、重新打开换新令牌;撤销授权同时作废会话;授权失效后仍能注销,未知令牌不能;失去 `ai.mcp/access` 时会话立即作废而授权保留;审计含行知任务号和拒绝记录。原有四个测试无回归。行知联调:行知服务器上的内置浏览器(Playwright 驱动 Edge)用本接口免密码打开本地实例的后台页面并读取内容,AI 触发的写请求停在审批(本地联调时后台前端的生产构建写死了线上接口地址,用请求改写指向本地实例)。
|
||||
AI 后台浏览器(同日增补):`AiMcpConsoleTest` 通过——无效授权 401;没有 `ai.mcp/console` 被拒且不建会话;签发 terminal=8 会话、有效期 2 小时,关闭“多处登录”时电脑端登录不受影响;换来的令牌能直接访问后台接口;再次打开沿用同一会话;关闭后令牌立即失效、重复关闭无害、重新打开换新令牌;撤销授权同时作废会话;授权失效后仍能注销,未知令牌不能;失去 `ai.mcp/access` 时会话立即作废而授权保留;审计含行知任务号和拒绝记录。原有四个测试无回归。
|
||||
|
||||
物流查询(同日增补):`AiMcpLogisticsTest` 通过——只查得到列表范围内的订单(别人的订单按订单号、快递单号都查不到);已同步轨迹的状态、签收时间、最新在前、条数限制、来源与更新时间;收件人电话、患者电话和轨迹文字里的手机号脱敏,不返回详细地址;没有「物流轨迹」权限时不给轨迹;未填单号;同名患者分开列出,按患者ID给出最近一单完整轨迹,日期范围筛选;库里没有轨迹时走实时查询且不写库(测试实例关闭了快递100 / 京东接口,不外呼),第 31 次实时查询被拒;参数校验;工具中文名;审计。其余五个测试无回归;行知端确认 MCP 客户端拿到工具中文名。行知联调:行知服务器上的内置浏览器(Playwright 驱动 Edge)用本接口免密码打开本地实例的后台页面并读取内容,AI 触发的写请求停在审批(本地联调时后台前端的生产构建写死了线上接口地址,用请求改写指向本地实例)。
|
||||
|
||||
## 7. 回退
|
||||
|
||||
@@ -145,3 +166,4 @@ AI 后台浏览器(同日增补):`AiMcpConsoleTest` 通过——无效授
|
||||
- 阶段三可选:接入 IAM(Keycloak)授权码 + PKCE 绑定,密码不再经过行知。
|
||||
- AI 后台浏览器按请求方法区分读写:行知只拦截非 GET/HEAD/OPTIONS 请求。后台若有用 GET 修改数据的接口,无法被审批拦住,建议这类接口改为 POST(导出下载在行知浏览器里已禁用)。
|
||||
- 取消角色的 `ai.mcp/console` 只阻止新开会话,不会立刻结束已打开的会话(见第 7 节)。
|
||||
- 物流轨迹的时效取决于服务器上的同步任务;自营手工填单号的订单可能从未生成轨迹记录,只能靠实时查询(有次数上限)或官网查询页。
|
||||
|
||||
Reference in New Issue
Block a user