# Ai-DOP 第三方对接模拟器 独立运行的本地第三方系统模拟器,用同一套可追踪业务样例分别模拟三种入站方式: | 通道 | 模拟端 | Ai-DOP 真实执行链 | |---|---|---| | `DB_SYNC` | `providers/mock_db.py` → 独立 MySQL schema `aidop_integration_sim` | `MdpSourcePullDispatcher` → `MdpDbPullExecutor` | | `API_PULL` | `providers/mock_api.py` → 本地 GET Mock(默认 8018) | `MdpSourcePullDispatcher` → `MdpApiPullExecutor` | | `API_INBOUND` | `clients/inbound_client.py` → HMAC 签名推数 | `MdpInboundController` → 接收服务 | 派生文档:[`建设方案`](../../doc/plan/系统集成/Ai-DOP第三方对接模拟器建设方案.md) · [`分阶段完整执行任务书`](../../doc/plan/系统集成/Ai-DOP第三方系统模拟器分阶段完整执行任务书.md) · [`人工操作手册`](../../doc/plan/系统集成/Ai-DOP第三方系统模拟器人工操作手册.md) 三阶段能力均已交付:第一阶段基础打通(F1-F5)→ 第二阶段异常测试(N1-N4)→ 第三阶段受控压力测试(§16-§21)。 **本工具不写 Ai-DOP 贴源/标准/KPI 表,不修改 Ai-DOP 配置,不进入 Ai-DOP 菜单。** --- ## 1. 环境要求 - Python 3.11+(Windows 需真实解释器,非 Microsoft Store 别名占位) - 独立 MySQL 测试实例,具备建库建表权限;**不得**使用 Ai-DOP 库或其账号 - 可访问的 Ai-DOP 开发/UAT 地址,且已准备测试租户与测试 AccessKey ```powershell cd d:\DEMONET python -m venv .venv-sim .venv-sim\Scripts\pip install -r tools/integration-simulator/requirements.txt ``` ## 2. 配置 ```powershell Copy-Item tools/integration-simulator/config.example.json tools/integration-simulator/config.json # 按本机修改 aidopBaseUrl / mockApiPort / allowedTargetHosts ``` `config.json` 已加入 `.gitignore`,且**只放非敏感项**。凭据一律走环境变量: ```powershell $env:AIDOP_SIM_MYSQL_HOST = "127.0.0.1" $env:AIDOP_SIM_MYSQL_PORT = "3306" $env:AIDOP_SIM_MYSQL_USER = "sim_user" # 专用账号,勿用 Ai-DOP 主库账号 $env:AIDOP_SIM_MYSQL_PASSWORD = "<模拟库密码>" $env:AIDOP_SIM_MYSQL_DATABASE = "aidop_integration_sim" $env:AIDOP_SIM_INBOUND_ACCESS_KEY = "<测试 AccessKey>" $env:AIDOP_SIM_INBOUND_SECRET = "<测试 Secret>" $env:AIDOP_SIM_ADMIN_TOKEN = "<可选:只读管理 API 用>" ``` 缺任何一项时,页面顶部横幅会明确提示缺哪个变量;Secret 永远不由页面输入、不回显、不落盘。 ## 3. 启动 ```powershell .venv-sim\Scripts\python tools/integration-simulator/app.py # 控制台:http://127.0.0.1:8017/ ``` 服务只绑定 `127.0.0.1`。目标 Ai-DOP host 必须命中 `allowedTargetHosts`,否则硬阻断。 ## 4. 页面用法 | 页签 | 用途 | |---|---| | 对象测试 | 选对象 → 选通道 → 加载/编辑适配样例 → 预检 → 执行 → 看三层结论与核验 SQL | | 业务场景 | 三组跨对象链路(订单制造链 / 采购执行链 / 库存与成品链);不支持的节点标红「该通道不支持」 | | Mock API | 启停 8018;切换 NONE/TOKEN/BASIC/APIKEY;注入延迟与 401/403/429/500;改响应路径;看脱敏审计 | | 模拟 MySQL | 建库建表 / 装载样例 / 重置单表 / 行数与最大增量 / 生成 Ai-DOP 配置清单 | | API_INBOUND | 26 契约 schema 批量预检;七类操作与必测用例 | | 异常测试 | 三通道异常场景目录(方式甲 10 / 方式乙 16 / 方式丙 17);auto 一键预置/恢复,guide 只出指引;N4 八列异常报告 | | 压力测试 | 计划十项必填 + 通道参数 + 开始/停止 + 实时指标 + 报告导出;方式甲 S/M/L 档、方式乙 Mock 压力模式、方式丙并发/QPS/阶梯 | | 运行记录 | 最近运行(自动脱敏) | | 环境安全 | 凭据加载状态、白名单、隔离约束说明 | ## 5. Ai-DOP 侧登记(人工执行) 模拟器只**生成建议配置**(`GET /api/db/aidop-config/{objectCode}`),不自动写库。需在 Ai-DOP 配置页登记: - `mdp_source`:三个专用测试来源 `SIM_MODE3_DB` / `SIM_MODE3_API` / `SIM_MODE3_INBOUND`,共用 `system_code=SIM_MODE3`; - `mdp_entity`:源表/源 API path、目标贴源表、业务键、增量列(DB_SYNC 用 `sourceUpdatedAt`,API_PULL 用 cursor 语义); - `mdp_inbound_grant` + `SysOpenAccess`:测试 AccessKey 授权目标 entityCode 并绑定测试租户; - `mdp_entity.inbound_enabled=1`。 三通道**不得共用 source_code**,且不得抢占现有 T8/165/自建单等生产权威来源。 ## 6. 结果分层(不得混为一谈) ```text 代码层:SUPPORTED / UNSUPPORTED 配置层:READY / CONFIG_NOT_REGISTERED / CONFIG_NOT_ENABLED / GRANT_MISSING / SOURCE_UNREACHABLE 数据层:NOT_RUN / ACCEPTED / STAGING_WRITTEN / TRANSFORM_PENDING / RUN_FAILED ``` 第一阶段无安全的只读管理 API 核对贴源结果时,报告 `TRANSFORM_PENDING` 并给出人工核验 SQL, **不把 HTTP 202 说成业务全链路跑通,不把配置未开通说成代码未实现**。 ## 7. 测试 ```powershell .venv-sim\Scripts\pytest tools/integration-simulator/tests -v ``` | 文件 | 覆盖 | |---|---| | `tests/test_inbound_signature.py` | 签名金标向量(与独立实现交叉验证)、SHA256/HMAC、签名头不外泄 | | `tests/test_sample_adapters.py` | 目录三通道完整性、26 契约覆盖、样例路径存在、`SIM-{runId}-` 业务键、契约字段名、缺必填如实上报 | | `tests/test_mock_api.py` | cursor 过滤、四种鉴权、401/403/429/500 注入、响应路径切换、样例挂载、审计脱敏 | | `tests/test_mock_api_lifecycle.py` | 真实端口启停生命周期:绑定成功且可连通、重复调用幂等、端口占用如实报错(回归 uvicorn 0.30+ 无 `startup_error` 导致的 500) | | `tests/test_mock_db.py` | DDL 与类型推断、幂等 upsert、库名硬阻断、配置清单;真实 MySQL 用例无密码时跳过并标注未实测 | | `tests/test_guard.py` | 非白名单目标阻断、预检状态分类、运行记录脱敏与容量 | | `tests/test_redaction.py` | Secret/连接串/Bearer/AccessKey 后四位脱敏 | | `tests/test_fault.py` | 三通道异常目录计数、auto 预置/恢复、guide 不落库、N4 报告 upsert、expectedReject 与工具失败严格区分 | | `tests/test_load_controls.py` | 压测状态机、计划十项必填、双层上限、租户字段剥离、三类自动停止 | | `tests/test_load_metrics.py` | 19 项指标、滚动窗口 QPS、停止后指标冻结、跨轮重置 | | `tests/test_auth_probe.py` | 入站实体授权探测:过滤未授权、不可达快速返回、缺凭据、缓存 | > `tests/test_guard.py`(安全闸门与三层判定)与 `tests/test_mock_api_lifecycle.py`(真实端口启停)是任务书初始清单外新增的守卫测试,其余文件名与任务书一致。 > 基线:171 passed 1 failed(唯一失败为 `test_live_schema_load_and_increment`,需真实远端 MySQL 且当前不可达,属环境性)。 ## 8. 目录 ```text tools/integration-simulator/ ├─ app.py # 本地控制服务(只绑 127.0.0.1)+ 三层报告 + 运行记录 ├─ requirements.txt ├─ config.example.json # 仅非敏感默认项;复制为 config.json(已 gitignore) ├─ clients/ │ ├─ inbound_client.py # HMAC 签名与七类推数操作 │ ├─ aidop_probe.py # 只读预检与白名单闸门 │ └─ redaction.py # 脱敏 ├─ providers/ │ ├─ mock_api.py # API_PULL 对端 + 异常注入 + 压力模式统计 │ ├─ mock_db.py # MySQL 建表/装载/清理 + 配置清单 + S/M/L 档位生成 │ └─ fault_catalog.py # 三通道异常场景目录(10/16/17) ├─ load/ │ ├─ controls.py # 压测状态机、计划校验、自动停止 │ ├─ metrics.py # 19 项实时指标 │ └─ runner_p3.py # 方式丙并发执行器(QPS/阶梯/错误比例) ├─ catalog/ │ ├─ entities.json # 42 个对象 × 三通道能力矩阵 │ ├─ scenarios.json # 三组跨对象基础场景 │ └─ sample_adapters.py # canonical 样例 → 三通道适配 ├─ static/ # 本地页面 └─ tests/ ``` 复用但**不迁移、不修改**的既有资产: ```text doc/db/mdp/mock_api/endpoints.json doc/db/mdp/mock_api/samples/*.json tools/mock/inbound/push_demo.py ``` ## 9. 能力边界 - 只做 MySQL 方言,不做 SQL Server;全链路自动对账、CLI/CI/容器化未做(Docker 属可选交付); - 压测为受控压力测试:默认关闭、计划十项必填、负责人确认、不允许生产环境、双层上限、连续错误自动停止; - 不为无契约对象伪造 API_INBOUND 接口(销售订单头、合同评审、发货计划/发货单、工序计划、MRP、采购申请、交货计划、现存量、过程检验、成品入库); - 删除测试数据不由模拟器执行,只提供运行编号与核验 SQL 清单; - 本工具只模拟第三方一侧:不写 Ai-DOP 贴源/标准/KPI 表,不修改 Ai-DOP 配置,不进入 Ai-DOP 菜单。 ## 10. 常见故障排查 | 现象 | 原因 | 处理 | |---|---|---| | 启动报 `[Errno 10048]` 端口被占用 | 上一个模拟器进程未退出仍监听 8017 | `netstat -ano | findstr :8017` 找 PID 后 `Stop-Process -Id -Force` 再启动(`Get-NetTCPConnection` 偶发假阴性,以 netstat 为准) | | 预检 `reachable=false` + WinError 10061 | Ai-DOP 后端 :5005 未启动(无人监听而非防火墙) | 启动 Admin.NET.Web.Entry;`GET /api/precheck` 返回 `httpStatus=405` 属正常可达 | | 入站执行全 403 `entity not authorized` | 凭据有效但该 entityCode 未授权 | Ai-DOP 侧 `mdp_inbound_grant` + `SysOpenAccess` 补授权;压力页签实体下拉可点「刷新授权」重探测 | | 压力页签实体下拉为空 | ① 浏览器缓存旧 HTML 与新 JS 错配 ② Ai-DOP 不可达 | ① 强刷(Ctrl+F5);② 启动 Ai-DOP 后点「刷新授权」 | | P3 压测请求约 2 秒/条且全失败 | Windows 连接拒绝默认 SYN 重试(Ai-DOP 未启动时) | 属预期表现,连续错误会自动停止;先启动 Ai-DOP 再压测 | | MySQL `1045 Access denied` 或 `'sim_user'@'localhost'` | 账号按 `user@host` 匹配,公网连远端时来源 IP 是本机出口 IP | 为出口 IP 显式建号授权(`Invoke-RestMethod https://api.ipify.org` 查 IP),或用 SSH 隧道 `ssh -N -L 13306:127.0.0.1:3306 user@host` | | MySQL `caching_sha2_password` 认证失败 | PyMySQL 无 TLS 下需 RSA 公钥交换 | `pip install cryptography`(requirements 未声明,需手动装) | | 入站请求被 mock 客户端签名不一致 | 环境变量注入的进程才能读到 `AIDOP_SIM_INBOUND_*` | 环境变量改后必须**重启模拟器进程**(启动后无法热更新) | | PowerShell 里 `python` 打开应用商店 | Microsoft Store 别名占位 | 使用真实解释器(如 `.venv-sim\Scripts\python`),并确保 venv 优先于别名 | | PowerShell `curl` 行为异常 | `curl` 是 `Invoke-WebRequest` 别名 | 用 `curl.exe`;传 JSON 用 Python urllib 或文件方式更稳 | | 压测停止后指标仍变动(历史版本) | 滚动窗口实时计算随时间衰减 | 现版本停止时冻结指标,停止后不再变动 |