真实 MES HTTP 适配器(config-driven)
round-45 方向 II · 验收矩阵 73/75 推进(round-46 方向 LL:对接就绪检查 readiness)
模块:server/integrations/mes_http.py(integ-mes-http,可复用 ✔)
目标
- 矩阵 73(「对接至少一个真实 MES 测试环境;鉴权、幂等、超时、重试、回执和补偿通过」):
真实工厂需外部环境,本轮以 config-driven HTTP MES 适配器框架 落地——
HttpMesClient
可指向任意 base URL 的真实 MES,用本地 HTTP stub 黄金测试覆盖鉴权/幂等/超时/重试/回执。
- 矩阵 75(「真实 MES/WMS 适配器(现场对接)」):适配器已注册进 MCP 总线
(plugin
mes.http,工具 mes.http_dispatch 等 + power 门禁),现场对接只需配置。
与 Mock stub 的切换
- 默认 = 进程内 Mock(
server/integrations/mes_stub.py,MockMesClient),不依赖任何网络。
- 显式配置
MES_HTTP_BASE_URL 后 = HTTP 适配器:server/aps_domain/mes.py 的
_get_active_client() 按环境变量选择客户端,接口完全一致(status / create_work_order / post_report / cancel_work_order),mes.dispatch P3 门禁与 mes.cancel_dispatch
saga 补偿路径不受影响。
# 启用 HTTP 适配器(现场/联调环境)
export MES_HTTP_BASE_URL=https://mes.example.com/api
# 切回本地 Mock(默认):不设置 MES_HTTP_BASE_URL 即可
环境变量
| 变量 |
默认 |
说明 |
MES_HTTP_BASE_URL |
(空 = fail-closed) |
真实 MES / 本地 stub 的根地址,如 http://127.0.0.1:8765 |
MES_HTTP_TOKEN |
空 |
鉴权 token;配合 MES_HTTP_TOKEN_HEADER 使用 |
MES_HTTP_TOKEN_HEADER |
Authorization |
token 所在头;Authorization 自动补 Bearer 前缀 |
MES_HTTP_TIMEOUT_SECONDS |
10 |
单次请求超时(秒) |
MES_HTTP_MAX_RETRIES |
2 |
指数退避重试次数(连接失败/超时/5xx/429;4xx 不重试) |
MES_HTTP_IDEM_HEADER |
X-Idem-Key |
幂等键所在头(也可放 body.idemKey) |
token 仅由 HttpMesClient 从环境/构造参数读取,不写入 plugins.json(MCP manifest
的 auth 字段保持为空)。
端点约定(适配器 → MES)
| 操作 |
方法/路径 |
说明 |
| 健康/连接 |
GET /health |
返回 {ok, system, plant, woCount, ...} |
| 下发(幂等) |
POST /work-orders |
body 携带工单数据 + idemKey;重复 idemKey 返回 {duplicate:true, externalWo} |
| 状态查询 |
GET /work-orders/{externalWoId} |
外部工单状态轮询 |
| 报工回执 |
POST /work-orders/{externalWoId}/reports |
回流 progressPct/qtyDone/status |
| 撤单(幂等) |
POST /work-orders/{externalWoId}/cancel |
saga 补偿;重复撤单返回 duplicate:true |
失败语义(供 saga 补偿)
MesHttpError{code, status_code} 显式抛出,mes.cancel_dispatch 会把 HTTP 失败记为
failed("<CODE>:<externalWoId>")而不是静默跳过:
| code |
触发 |
MES_HTTP_NOT_CONFIGURED |
未配置 MES_HTTP_BASE_URL(fail-closed) |
MES_HTTP_CONNECT_FAILED / MES_HTTP_TIMEOUT |
连接失败 / 超时(重试耗尽后) |
MES_HTTP_AUTH_FAILED |
401/403 鉴权失败 |
MES_HTTP_UPSTREAM_ERROR |
5xx/429(重试耗尽后) |
MES_HTTP_NOT_FOUND / MES_HTTP_REJECTED / MES_HTTP_INVALID_RESPONSE |
404 / 其他拒绝 / 响应不合法 |
MCP 总线注册(矩阵 75)
- 总线启动即注册 plugin
mes.http(manifest + 工具契约 + power 门禁,fail-closed):
| 工具 |
权力 |
幂等 |
说明 |
mes.http_dispatch |
P3 |
✔ |
真实 MES 下发(需显式放行权限) |
mes.http_status |
P0 |
✔ |
连接/外部工单状态(只读) |
mes.http_report |
P2 |
✘ |
报工回执 |
mes.http_cancel |
P2 |
✔ |
撤单(saga 补偿) |
- P2/P3 写工具默认 deny,调用前需
set_permission(plugin="mes.http", tool=..., allow=True);
未配置 MES_HTTP_BASE_URL 时任何工具调用抛 MesHttpError MES_HTTP_NOT_CONFIGURED(fail-closed)。
对接就绪检查(readiness,矩阵 73 现场对接准备)
HttpMesClient.readiness(probe=False) 返回稳定 JSON 字段(前端可直接展示,不抛错):
| 字段 |
类型 |
说明 |
configured |
bool |
是否已配置 MES_HTTP_BASE_URL(fail-closed) |
baseUrl |
string/null |
当前 base URL;未配置为 null |
tokenPresent |
bool |
是否已配置 MES_HTTP_TOKEN |
timeoutSec |
number |
单次请求超时(秒) |
maxRetries |
int |
重试次数 |
connectivity |
"unknown"/"ok"/"failed"/null |
未配置 null;配置未探测 unknown;探测后 ok/failed |
lastError |
{code,message,statusCode}/null |
探测失败时记录最近一次错误,成功/未探测为 null |
message |
string |
中文提示(如「未配置 MES_HTTP_BASE_URL」「连通正常」) |
readiness()(不探测):只返回配置视图,connectivity="unknown",不发网络请求。
readiness(probe=True):对 base_url 发轻量 GET /health,短超时(min(timeout, 3s));
失败不抛错,记录 connectivity="failed" + lastError。
- 未配置时
configured=false,connectivity=null,message 明确提示(网关 200 不 500)。
网关端点
| 方法/路径 |
权力 |
说明 |
GET /api/integrations/mes/readiness |
P0 只读 |
返回 readiness() 配置视图 |
POST /api/integrations/mes/readiness/probe |
P1 |
触发一次连通性探测(GET /health 短超时) |
curl -s http://127.0.0.1:8003/api/integrations/mes/readiness
# {"configured":false,"baseUrl":null,"tokenPresent":false,"timeoutSec":10.0,
# "maxRetries":2,"connectivity":null,"lastError":null,"message":"未配置 MES_HTTP_BASE_URL:..."}
现场对接步骤
- 获取真实 MES 的 base URL 与 token,配置
MES_HTTP_BASE_URL / MES_HTTP_TOKEN。
- 对齐端点:若真实 MES 路径不同,改写
HttpMesClient 各方法中的 path 常量即可(框架已隔离)。
- 校验:
GET /api/integrations/mes/readiness 返回 configured=true,
POST /api/integrations/mes/readiness/probe 返回 connectivity="ok";
mes_connection_status() 返回 mode=http, connected=true。
- 权限放行:管理台/API 对
mes.http_dispatch 等工具 allow=True。
- 联调:小批量下发 → 验证 MES 侧工单创建;重复 idemKey 验证幂等;断网/5xx 验证超时重试与补偿。
测试
.venv\Scripts\python.exe -m pytest tests/golden/test_mes_readiness.py tests/golden/test_mes_http.py tests/golden/test_mes.py -q
- 本地 stub:FastAPI + uvicorn,随机端口(不占用 8003/5173)。
- 黄金覆盖:readiness(未配置 fail-closed / 字段齐 / 探测 ok/failed / 网关接线)+ 正常下发+回执 / 幂等 / 超时 / 5xx 重试恢复 / 重试耗尽 / 鉴权失败 /
fail-closed / mes.py 域切换(stub 默认,HTTP 显式)/ MCP 总线端到端。
边界与剩余风险
- 矩阵 73 的真实工厂对接仍需外部环境:本轮的 HTTP 框架 + 本地 stub 黄金测试覆盖了
鉴权/幂等/超时/重试/回执/补偿的适配器侧语义;真实 MES 端点路径/报文差异需现场配置联调。
- 适配器假设 MES 侧以
idemKey 头/字段去重;真实 MES 若用不同幂等协议需在 create_work_order 内适配。
- token 经环境变量注入,未做密钥管理(生产可接入 KMS/Secrets 提供方)。