本文回答一个很容易判断错误的问题:“DGJ2 的开放接口入口在哪里?”
答案不是一个 URL 或一个 Controller。当前仓库同时保留交易码映射型 OpenAPI、APP 交易码入口、SAAS 交易码入口、积分商城签名入口、令牌式开放页面、全车件代理入口、BaseApiController 直连 JSON API,以及名为 v2 但仍属于 PC 登录态的接口。它们的请求格式、鉴权、上下文、响应、日志和幂等完全不同。
本文只公开接口字段名、代码路径和验证方法,不记录任何签名密钥、令牌值、Cookie、生产主机或内部网络凭据。静态代码无法证明生产网关、WAF、IP 白名单和调用方实际流量,相关结论统一标为待环境确认。
1. 先读结论:八类入口不能混用
| 家族 | 入口 | 路由方式 | 主要鉴权 | 典型调用方 |
|---|---|---|---|---|
| 老 OpenAPI | /Openapi/index | transCode -> apis.php -> service/api | 代码内未见签名;可能依赖外部网关 | NC、OMS、商城等历史系统 |
| APP 映射 API | /Carsteward/index、index2 | tradeCode -> appapis.php | 数据签名+登录 Session;存在历史旁路 | 站管家 APP、部分全车件来源 |
| 老 SAAS 映射 API | /OpenSaasApi/index | transCode -> SaasApis.php | 代码内未见签名;可能依赖网关 | 快维/SAAS 历史调用 |
| 积分商城映射 | /PointMall/index | tradeCode -> appapis.php | 独立签名算法 | 积分商城 |
| 令牌式 OpenSaaS | /opensaas/Order/* | 显式 Controller 方法 | pr 令牌查 Redis 客户上下文 | VIP 下单/支付页面 |
| 全车件代理 | /allcarpart/** | 显式路由+三段通配 | PC Session+服务站功能权限 | DGJ PC 全车件模块 |
| 新直连 API | /inner/** 等 | Controller/方法显式路由 | Inner Token/网关头/网络边界,按入口不同 | 内部微服务、OPS、任务 |
PC v2 | /v2/** | Controller/方法 | 普通 PC Session 和菜单权限 | 新版 PC 页面 |
flowchart TB
C[调用方] --> G{入口家族}
G --> O[Openapi/transCode]
G --> A[Carsteward/tradeCode]
G --> S[OpenSaasApi/transCode]
G --> P[PointMall/tradeCode]
G --> T[opensaas/pr token]
G --> F[allcarpart/session proxy]
G --> I[inner/direct JSON]
G --> V[v2 PC session]
O --> Legacy[application/service/api]
A --> App[application/service/api/app]
S --> Saas[application/service/api/saas]
P --> App
T --> Vip[VipOrderSer]
F --> Micro[全车件微服务]
I --> Domain[application/Services]
V --> PcService[PC Service]
2. 文档目标和边界
本文重点说明:
- 请求如何从 URL 和交易码定位到真实 PHP 类。
- 每类入口要求哪些 envelope、Header、Session 或 Token。
- 登录用户、服务站、来源系统和请求 ID 如何注入业务数据。
- 成功、失败、重复请求、弃用接口分别返回什么。
- 请求日志、Mongo 幂等记录、Redis 锁和业务表怎样关联。
- 老字段、状态、错误码、大小写和 JSON 字符串为何不能随意修改。
- 如何在不破坏历史调用方的前提下迁移到显式版本接口。
本文不逐项重写 248 个映射项的业务逻辑。采购、销售、库存、活动、财务等真实业务规则仍以对应专题为准;本篇负责解释它们从开放入口怎样进入。
3. 数量和存量规模
通过当前配置和文件静态审计得到:
| 对象 | 数量 | 说明 |
|---|---|---|
apis.php 交易码 | 39 | 老 OpenAPI |
SaasApis.php 交易码 | 20 | 老 SAAS |
appapis.php 交易码 | 189 | APP、WMS、盘点、配送等 |
service/api PHP 文件 | 208 | 包含上述多个家族 |
service/api/app PHP 文件 | 151 | APP 映射实现 |
service/api/saas PHP 文件 | 18 | SAAS 映射实现 |
controllers/allcarpart PHP 文件 | 23 | 全车件代理/导入/打印 |
controllers/v2 PHP 文件 | 6 | PC v2 Controller |
配置映射到物理文件的静态检查发现:老 OpenAPI 2 个、SAAS 2 个、APP 39 个映射找不到对应路径。缺失可能代表已废弃、迁移后遗留、配置拼写或发布包不完整,不能直接删除,也不能把“配置中存在”当成“接口可用”。
4. 各入口的信任边界
flowchart LR
Internet[外部/客户端] --> Edge[DNS/WAF/API网关/负载均衡]
Edge --> DGJ[DGJ2 Controller]
DGJ --> Auth{应用内验证}
Auth -->|签名| AppAPI[APP/PointMall]
Auth -->|Session+权限| ACP[AllCarPart/v2]
Auth -->|Redis令牌| OpenSaaS[令牌式OpenSaaS]
Auth -->|InnerToken/Header| Inner[直连内部API]
Auth -->|代码内未见验证| Legacy[Openapi/OpenSaasApi]
“代码内未见验证”不等于生产公网裸露。可能还有 Caddy、网关、VPN、IP 白名单或安全组保护;但也不能因为“应该有网关”就假定安全。上线审计必须同时验证网络层和应用层。
5. 老 OpenAPI 请求 envelope
application/controllers/Openapi.php 接收 POST 表单并交给 OpenService::parse。
代表性请求:
POST /Openapi/index
Content-Type: application/x-www-form-urlencoded
transCode=shmstockOut&reqCode=OMS&targetCode=SMH&requestId=req-unique-id&data=[{"nid":"PO...","billNo":"SH...","sku_id":"SKU...","qty":1,"price":10,"amount":10}]
| 字段 | 是否必填 | 含义 | 注意事项 |
|---|---|---|---|
transCode | 是 | 配置交易码 | 老 OpenAPI 使用 transCode,APP 使用 tradeCode |
reqCode | 是 | 请求方系统代码 | 同时影响响应编码和日志来源 |
targetCode | 是 | 目标系统代码 | 当前分发主要不依赖其值,但合同仍要求 |
data | 是 | 业务数据 | 很多 Service 把它当 JSON 字符串再次 decode |
requestId | 条件必填 | 请求唯一 ID | 存在时启用 Redis 锁和 Mongo 幂等状态 |
6. 老 OpenAPI 完整分发链
sequenceDiagram
participant X as NC/OMS/商城
participant C as Openapi Controller
participant O as OpenService
participant R as Redis request lock
participant M as Mongo dgj_api_log
participant S as 动态Service
participant D as MySQL业务表
X->>C: POST envelope
C->>O: parse(str_enhtml(post))
O->>O: checkParams
opt requestId存在
O->>R: SET NX EX 600s
end
O->>M: INSERT/查询请求状态
alt 相同requestId已成功
O-->>X: 通用success,不再执行业务
else 首次或上次未成功
O->>O: transCode查apis.php
O->>S: 加载映射类并调用 service 方法
S->>D: 校验和业务读写
S-->>O: _success/_error直接输出
O->>R: 释放锁
O->>M: 成功回调status=1
end
7. transCode 动态定位规则
以配置为例:
shmstockOut => invoice.StockOut
解析步骤:
- 从
apis.php取映射字符串。 - 把
.换成/,得到invoice/StockOut。 - 拼接
application/service/api/invoice/StockOut.php。 include_once文件。- 对末段执行
ucfirst,实例化StockOut。 - 调用统一方法
service($params['data'])。
flowchart LR
A["transCode=shmstockOut"] --> B["apis.php"]
B --> C["invoice.StockOut"]
C --> D["invoice/StockOut"]
D --> E["service/api/invoice/StockOut.php"]
E --> F["new StockOut"]
F --> G["service data"]
Linux 文件名大小写敏感。修改配置或重命名类时,要同时验证映射值、文件名、类名和部署系统大小写,不能只在默认不区分大小写的开发磁盘上测试。
8. 老 OpenAPI 交易码分组
| 目录 | 映射数 | 代表用途 |
|---|---|---|
invoice | 15 | 审核、采购出库、关闭、修改、预订单收款/核销 |
services | 6 | 服务商新增、修改、重置密码、登录、Session 校验 |
goods | 4 | 商品编辑、上下架、价格、测试接口 |
advanceOrder | 4 | 预订单列表、数量、详情、审核 |
topservice | 3 | 商城收款、取消订单、修理厂新增 |
goodsInfo | 3 | 销售组织、结算价、商城筛选 |
users | 2 | 手机号、全车件客户信息 |
| 其他 | 2 | 分类、服务商类型 |
shmInfoEdit、shmGatheringBillApprove 等配置仍存在,但实现直接返回“接口已弃用”。这是一种保持旧调用方不报 404 的兼容策略。
9. 老 OpenAPI 参数校验和错误码
OpenService::checkParams 使用以下通用错误码:
| 错误码 | 触发条件 | 含义 |
|---|---|---|
E10001 | 整体参数为空 | 请求参数格式不正确 |
E10002 | data 为空 | 请求体格式不正确 |
E10003 | transCode 为空 | 交易码为空 |
E10004 | reqCode 为空 | 请求系统代码为空 |
E10005 | targetCode 为空 | 目标系统代码为空 |
E10006 | 交易码不存在 | 无效交易码 |
E10008 | Redis NX 加锁失败 | 相同重复请求 |
E50001 | 映射文件不存在 | 接口类不存在 |
错误码和中文文案可能被历史调用方直接判断。改文案也可能是破坏性变更,迁移时应先通过调用日志确认对方判断的是 status、error_code 还是文本。
10. requestId Redis 锁
stateDiagram-v2
[*] --> 无锁
无锁 --> 持锁: SET key NX EX 600
持锁 --> 重复拒绝: 并发请求SET失败
持锁 --> 无锁: _success/_error释放
持锁 --> 自动过期: 进程崩溃/未走统一响应
自动过期 --> 无锁
锁的目的只是防止 10 分钟内并发执行,不是永久幂等记录。_success 和 _error 从原始 POST 中读取 requestId 后释放锁;若 Service 直接 die/echo/throw 而未走统一响应,锁只能等待过期。
11. Mongo 请求幂等状态
OpenService 使用 Mongo collection dgj_api_log:
| 字段 | 含义 |
|---|---|
requestId | 外部请求唯一 ID |
| 原始 envelope | 当前代码把参数整体插入 |
status=0 | 已接收但未通过成功回调完成 |
status=1 | Service 调用 _success 后更新成功 |
createTime/modifyTime | 创建和状态更新时间 |
stateDiagram-v2
[*] --> 未记录
未记录 --> 处理中: INSERT status=0
处理中 --> 已成功: _success callback status=1
处理中 --> 再执行: 锁释放后相同requestId重试
已成功 --> 快速成功: 相同requestId不再执行业务
高风险点:重复成功请求只返回新的“通用成功”,不会返回第一次的原始业务结果。若调用方需要本地单号、状态或明细,不能把这个响应当作首次结果重放。
12. requestId 正确使用规则
| 场景 | requestId 策略 |
|---|---|
| 网络超时重试同一次业务动作 | 必须复用原 requestId |
| 用户发起新的独立动作 | 必须生成新 requestId |
| 批量内多张业务单 | 最好每业务单独 ID,或明确批次原子语义 |
| 已成功但客户端没收到响应 | 先查询业务事实,再用同 ID 重试 |
| 上次业务失败并已回滚 | 可同 ID 重试,Mongo status=0 会允许再次执行 |
| 上次部分成功 | 不能盲重试;先按业务键核对每一行 |
13. 老 OpenAPI 统一响应
BaseService::_success 典型字段:
{
"transCode": "shmstockOut",
"reqCode": "OMS",
"targetCode": "SMH",
"requestId": "req-unique-id",
"params": "原data的兼容格式",
"status": "success",
"message": "调用成功!",
"result": {},
"totalNumber": 0
}
失败响应增加 error_code,但不同 Service 可能把错误信息或部分业务结果放进 result。reqCode=KW 时使用 JSON_UNESCAPED_UNICODE,其他来源使用默认 JSON 编码,这也是历史兼容差异。
14. 统一响应并不等于统一 HTTP 状态
_success/_error 都直接 die(json_encode(...)),代码中没有设置对应 4xx/5xx HTTP 状态。调用方很可能始终收到 HTTP 200,再读取 JSON status/error_code 判断业务结果。
迁移到标准 HTTP 状态时不能直接替换:老客户端可能只处理 200;网关重试策略也可能因为状态变化而改变。推荐新版本同时返回标准 HTTP 状态和稳定业务码,旧路径维持合同直到流量归零。
15. 接口日志演进
| 日志位置 | 当前用途 | 风险 |
|---|---|---|
Mongo dgj_api_log | 老 OpenAPI requestId 幂等状态 | 可能包含完整业务请求,需脱敏和保留期 |
ApiService/ApiService 日志 | _success/_error 记录 params/result/source | 大报文和敏感字段风险 |
t_sys_apis_log* | 历史接口日志表常量 | 多数 _saveLog DB insert 已注释 |
t_sys_apis_log4 | OpenSaasService 入口仍主动插入请求 | 是否持续增长、索引和清理待确认 |
APP logger app/app | 记录完整 APP 请求 | 可能包含 sign、手机号、业务数据 |
BaseApi logger inner/api | 记录 body 和 headers | 头部可能含内部身份信息 |
不要在公共文档或工单粘贴完整日志。排查只保留 requestId、交易码、业务单号、脱敏 sid/uid、状态和错误码。
16. 代表链路:shmstockOut 采购出库
shmstockOut -> invoice.StockOut 是理解老 OpenAPI 如何落业务事实的代表案例。
核心业务字段:
| 字段 | 含义 | 校验/用途 |
|---|---|---|
nid | 来源采购订单编码 | 用 saCode 或本地 billNo 找采购单 |
billNo | 外部出库/发运单号 | SH 按单号去重,TS 按单号+箱号去重 |
boxNo | 箱号 | 与 billNo 组成并发锁键 |
sku_id | SKU | 转换为本地 invId |
qty | 出库数量 | 0 直接跳过,非空校验 |
price/amount | 单价/金额 | 写采购出库明细 |
wmsNo | 物流单号 | 履约追踪 |
isGift | 赠品标志 | 区分采购明细 |
17. shmstockOut 数据流
sequenceDiagram
participant OMS as OMS/NC
participant O as OpenService
participant S as StockOut
participant G as t_bs_goods
participant PO as t_scm_po_order + 分片明细
participant OUT as t_scm_po_out_info
participant R as Redis lock
OMS->>O: transCode=shmstockOut,data=JSON数组
O->>S: service(data字符串)
S->>S: JSON decode+字段校验
S->>G: sku_id转invId
S->>OUT: 查billNo/boxNo重复
loop 每行
S->>PO: 按nid找采购主单和明细
S->>R: 锁billNo+boxNo 300秒
S->>S: 构造170410采购出库明细
end
S->>PO: 事务更新billStatus=4
S->>OUT: 批量新增出库明细
S-->>OMS: success/error envelope
18. shmstockOut 幂等和部分成功风险
- SH 前缀发现同
billNo已存在时整次报重复。 - TS 前缀按
billNo-boxNo过滤重复。 - 同一请求内再次出现相同单号箱号会跳过重复加锁。
- Redis 锁失败或数据库已有重复时,代码把
$flag=false后继续其他行。 - 只要
infoList非空,仍可能提交其他行并返回“调用成功”。 - 因此这是允许部分处理的批量接口,不能只看外层
status=success。
flowchart TD
A[一批出库行] --> B{逐行校验/去重/加锁}
B -->|有效| C[加入infoList]
B -->|重复/锁失败| D[flag=false并跳过该行]
C --> E{infoList是否非空}
D --> E
E -->|是| F[提交有效行并success]
E -->|否| G[返回数量0不处理]
重试前必须按 billNo+boxNo+skuId 查已落明细,不能因为外层成功或失败直接整批重放。
19. “弃用但返回成功”的兼容模式
goods/InfoEdit 和 invoice/receiptCallback 当前直接:
_success([], "该接口已弃用")
它的含义是“请求被兼容接收,但不再产生业务副作用”,不是业务处理成功。调用方若只看 status=success,可能误认为商品或收款已经同步。
推荐弃用过程:先记录调用量和调用方;返回结构增加机器可识别 deprecated=true/replacement 但不删除旧字段;推动调用方迁移;流量归零后再下线。
20. APP API envelope
Carsteward 入口使用 tradeCode,data 是 JSON 字符串。
POST /Carsteward/index
Content-Type: application/x-www-form-urlencoded
tradeCode=appsaleOrderList&reqCode=app_android&targetCode=SMH&sign=<signature>&data={"page":1,"rows":20}
常见 envelope 字段:
| 字段 | 作用 |
|---|---|
tradeCode | APP 映射交易码 |
reqCode | 客户端/来源标识 |
targetCode | 目标系统标识 |
sign | 对 data 标量字段计算的历史签名 |
data | JSON 字符串业务参数 |
device、uid | 历史设备校验入参;当前校验代码提前返回 |
verify | 历史旁路字段,当前存在安全风险 |
21. APP 签名算法的兼容规则
不记录密钥值,只描述算法:
- JSON decode
data。 - 字段名转小写用于不区分大小写排序。
- 按键名升序恢复原字段名。
- 只拼接非数组的
key=value,嵌套数组被忽略。 - 先对拼接字符串做一次 MD5,再与固定共享密钥标识组合后再次 MD5。
flowchart LR
A[data JSON] --> B[decode数组]
B --> C[键名大小写归一后排序]
C --> D[忽略数组值]
D --> E[key=value&...]
E --> F[第一次MD5]
F --> G[拼共享密钥标识]
G --> H[第二次MD5=sign]
兼容重点:字段大小写、数字转字符串、浮点精度、空值、布尔值、字段顺序和嵌套数组都可能改变签名。不能“顺手改成标准 HMAC”而不提供新版本。
22. APP 签名当前高风险事实
静态代码确认:
verify=yes时允许签名不一致继续执行。- 签名失败响应会把服务端计算出的签名写进错误信息。
Carsteward::build接收 JSON 后返回可用 envelope 和签名。- 签名共享密钥硬编码在代码中,无法独立轮换调用方。
- 签名只覆盖
data中的标量,不覆盖 envelope 和嵌套数组。 - 请求中没有强制时间戳、nonce 或签名级防重放。
flowchart TD
A[收到APP请求] --> B[计算服务端sign]
B --> C{sign相同}
C -->|是| D[继续]
C -->|否| E{verify是否yes}
E -->|是| D
E -->|否| F[返回含计算值的错误]
这些入口是否被网关限制不能由代码证明,但应用层应立即收敛:删除旁路、关闭签名生成入口、错误不回显计算值、密钥配置化并支持版本化轮换。
23. APP 登录白名单和来源旁路
无需登录检查的交易码:
apploginappMobileLoginappgetVersionappMobileLoginSmsCodeappMobileLoginSelectStation
除此之外,代码在 reqCode != 'qcj' 时才执行登录、设备、黑名单和强制下线检查。也就是说 reqCode=qcj 会跳过整段登录态验证。签名仍会执行,但结合签名旁路时风险更高。
生产必须确认:qcj 是否由可信网关固定注入、客户端能否自行伪造、允许访问哪些 tradeCode。
24. APP 登录上下文注入
sequenceDiagram
participant A as APP
participant O as OpenAppService
participant S as Session/Redis
participant D as 动态App Service
A->>O: envelope+data JSON
O->>O: 校验签名
alt 登录接口或reqCode=qcj
O->>O: 跳过登录检查
else 普通接口
O->>S: login_session
O->>O: 黑名单/强制下线
O->>O: data JSON decode
O->>O: 合并sid,user_id,user_name,areaCode等
end
O->>D: service(合并后的数组)
注入字段来自服务端登录态:sid、user_id、user_name、areaCode/province、eparchy。业务 Service 不应信任客户端同名字段;当前 array_merge($clientData,$serverData) 由后者覆盖前者,这一点必须保持。
25. APP 设备校验实际上被禁用
checkDevice 虽然查询 t_app_device,但查询后立即 return,后面的“设备不存在则强制下线”不可达。
flowchart LR
A[device+uid查询APP_DEVICE] --> B[立即return]
B -.不可达.-> C[不存在则Compulsory offline]
这意味着“单设备登录”不能靠当前方法成立。修复早退会改变大量存量用户登录行为,必须先统计设备数据完整性、老版本 device 字段、同账号多设备和客服操作流程,再灰度启用。
26. APP 映射分组
| 目录 | 映射数 | 代表业务 |
|---|---|---|
public | 28 | 登录、首页、商品、车型、版本 |
system | 28 | 客户、仓库、账户、系统设置 |
wms | 20 | WMS、签收、仓内操作 |
purchase | 16 | 采购、关闭、退货、MOQ |
invCheck | 16 | 盘点 |
transport | 14 | 骑手、轨迹、签收、取消 |
fund | 14 | 收付款 |
sale | 13 | 销售单和出库 |
person | 9 | 个人消息等 |
moveStore | 8 | 微仓/移动商城 |
workOrder | 7 | 工单 |
sale2 | 6 | 第二代销售接口 |
| 其他 | 10 | 调拨、全车件、积分商城、扩展 |
配置中 apphomePage 重复定义,PHP 最终保留最后一个值。重复键即使值相同也说明配置缺少自动校验。
27. APP 映射到缺失文件的审计结果
当前静态审计有 39/189 个映射找不到目标文件,集中在:
- 旧首页、Banner、发运展示。
- 旧商品图片/参数。
- 旧销售新增、详情修改、删除。
- 一批旧采购新增、关闭、退货、MOQ。
- 旧资金新增/修改。
- 旧系统客户和个人消息。
部分功能可能已经迁移到 sale2、直连 Controller 或新微服务,但配置没有清理。处理顺序必须是:线上访问日志 -> 客户端版本 -> 替代入口 -> 灰度告警 -> 下线,不可直接删映射。
28. APP 高精度坐标兼容
OpenAppService::setIniPrecision 试图对配送坐标相关交易码把 PHP precision 调为 16,以兼容高德 14 位小数。
但当前调用是 appSignKey($params_data),传入的是解码后的 data,方法却从参数中读取 tradeCode。如果业务 data 本身没有 tradeCode,这段兼容不会触发。
需要用真实配送请求验证:签名前 JSON decode 是否已经损失精度、客户端和服务端拼接字符串是否一致、经纬度是否应该用字符串传输。
29. APP 异常响应风险
OpenAppService::parse 捕获 Exception 后直接 print_r($e),而多数校验通过 splash 直接结束。结果是异常路径可能返回调试对象、HTML 或非统一 JSON。
这会导致:
- 客户端无法稳定解析。
- 堆栈或文件路径可能泄漏。
- HTTP 状态与业务状态不一致。
- 接口日志难以聚合错误码。
迁移时应保持旧客户端可识别字段,同时统一新增 code/message/request_id/data 响应层。
30. 老 SAAS 映射入口
OpenSaasApi -> OpenSaasService -> SaasApis.php -> service/api/saas/* 使用与老 OpenAPI 类似的 envelope:
POST /OpenSaasApi/index
Content-Type: application/x-www-form-urlencoded
transCode=shmOrderCreate&reqCode=KW&targetCode=SMH&data={"customerCode":"...","srcOrderNo":"...","details":[]}
它校验 data/transCode/reqCode/targetCode 和交易码,但代码内未见签名、requestId 锁或 Mongo 幂等。BaseController('api') 也会跳过普通 PC Session 校验。
31. 老 SAAS 交易码
20 个映射覆盖:
- 客户注册和状态。
- 供应商与库存查询。
- 订单创建、关闭。
- 活动列表和详情。
- 品牌、车型、分类、商品查询。
- VIP 下单和会员信息。
- 按手机号查客户、小程序下单。
静态检查中 shmGetCarModel、shmGetCars 没有目标文件,配置注释中又有“备用”语义,需确认是否从未上线或已由其他车型接口替代。
32. SAAS 创建销售单请求
代表性 shmOrderCreate 数据:
{
"customerCode": "external-customer-code",
"srcOrderNo": "external-order-no",
"srcOrderId": "external-order-id",
"description": "order remark",
"details": [
{
"srcOrderEntryId": "external-line-id",
"skuId": "SKU001",
"qty": 2,
"price": 15.5,
"amount": 31,
"deduction": 0,
"isGift": 0,
"activityId": 0
}
]
}
33. SAAS 创建销售单数据流
sequenceDiagram
participant K as 快维/SAAS
participant S as OrderCreate
participant B as t_bs_user_join/contact/admin
participant G as t_bs_goods
participant O as t_scm_sa_order分片
participant I as t_scm_sa_order_info分片
participant M as 通知MQ
K->>S: customerCode+srcOrderNo+details
S->>B: 校验注册、绑定服务站、主账号
S->>O: 按sid+srcOrderNo查重
S->>G: skuId转invId/指导价
S->>I: 事务逐行新增,qty写负数
S->>O: 新增170502销售主单
S->>I: 按srcOrderId回填iid
S->>S: commit
S->>M: 发送新订单提醒
S-->>K: 订单生成成功
34. SAAS 创建销售单关键口径
| 外部字段 | 本地字段/算法 | 注意事项 |
|---|---|---|
customerCode | 查客户绑定 sid/contact/uid | 一个客户必须绑定服务站 |
srcOrderNo | 主单来源单号 | 当前代码用它查重,是核心幂等键 |
srcOrderId | 主单/明细来源 ID | 明细回填 iid 时使用 |
srcOrderEntryId | 明细来源 ID | 行级追踪 |
qty | 明细写 -abs(qty) | 销售数量采用负数方向 |
qty*price-deduction | 行金额 | 代码另用传入 amount 累加总金额,需核对一致性 |
skuId | t_bs_goods.id=invId | 缺失商品的错误处理需回归 |
| 本地生成 | billNo=XD...、transType=170502 | 不由外部传入 |
风险:行明细金额按公式计算,但主单 sum2 累加传入的 arr['amount'],二者不一致时可能形成主明细金额差异。
35. SAAS 订单幂等和事务后副作用
stateDiagram-v2
[*] --> 未创建
未创建 --> 已创建: sid+srcOrderNo不存在,事务提交
已创建 --> 重复拒绝: 相同srcOrderNo再次调用
已创建 --> 通知失败: commit后sendMailToMq异常
通知失败 --> 已创建: 业务单仍存在
srcOrderNo 是比外层 requestId 更重要的业务幂等键。事务提交后发送提醒 MQ,通知失败不应重建销售单。数据库是否有 (sid,srcOrderNo) 唯一索引需生产 DDL 确认。
36. 令牌式 OpenSaaS 不是 OpenSaasApi
application/controllers/opensaas/Order.php 继承 OpenSaasController,使用 pr 查询 Redis:
KEY_VIP_ORDER_TOKEN + pr -> kw_customer_code/customer_id/customer_name/...
令牌可以来自 Query 或 JSON Body。校验成功后生成 $customerData,后续页面和接口不再要求 PC Session。
sequenceDiagram
participant U as 用户浏览器/SAAS
participant C as OpenSaasController
participant R as Redis token hash
participant O as opensaas/Order
participant V as VipOrderSer
U->>C: pr token
C->>R: HGETALL token key
alt 无token/失效/无客户编码
C-->>U: 非法请求/令牌失效
else 有效
C->>O: 注入customerData
O->>V: 方案/下单/支付/状态
V-->>U: R::success/error
end
日志当前记录 Query 和 Body,可能包含 pr。日志必须脱敏,Token 需要短有效期、单用途、可撤销和防重放策略。
37. VIP 下单子流程
| 方法 | 核心请求 | 数据/副作用 |
|---|---|---|
getPlanInfo | pr 上下文 | 返回客户、套餐、有效期、一次性 nostr |
submit | planId,nostr,startDate,endDate | 校验防重,创建 VIP 订单,缓存支付信息 30 分钟 |
toPay | pay_no | 获取支付二维码/URL |
status | pay_no | 查询订单支付状态 |
flowchart LR
A[有效pr] --> B[getPlanInfo]
B --> C[planList+nostr]
C --> D[submit planId+nostr]
D --> E[创建VIP订单/pay_no]
E --> F[Redis支付缓存30分钟]
F --> G[toPay]
G --> H[支付]
H --> I[status轮询]
38. 积分商城映射入口
PointMall -> OpenPmService 复用 appapis.php 动态加载 Service,但签名算法作用于整个请求 envelope,并排除 sign 字段;它与 APP 对 data 签名的算法不是同一合同。
高风险点:
- 同样使用历史共享密钥和双 MD5。
- 签名失败返回服务端计算值。
- 请求先写
t_sys_apis_log2,再加载 Service。 - 配置中只有 2 个
pointMall映射,但它理论上能按appapis.php查任意 tradeCode;是否由网关限制需确认。
39. 全车件代理的业务边界
全车件 PC Controller 继承 App\controllers\allcarpart\Controller,最终转发到 MICRO_URL + 当前URI。
sequenceDiagram
participant U as DGJ PC用户
participant B as BaseController
participant A as allcarpart Controller
participant P as StationSer权限
participant M as 全车件微服务
U->>B: Session请求
B->>B: 登录/账号/菜单校验
B->>A: 路由到具体Controller/action
A->>P: enableAllCarPart(sid)
alt 未开通或无权限
A-->>U: JSON status=500/message
else 已开通
A->>A: 解析JSON或使用内部data
A->>M: POST JSON + sid/uid/user_name headers
M-->>A: code/message/data
A-->>U: JSON响应
end
40. 全车件路由分类
| 路由组 | 代表路径 | 本地特殊处理 |
|---|---|---|
| 仓库/货位 | storage/storage-area* | 导入、区域转发 |
| 商品 | goods/good-search/* | 查询代理 |
| 采购 | purchase/goods/*、退货、发运 | 导入、打印、导出 |
| 销售 | sale/sa-order/*、出库、销退 | 导入、打印、导出 |
| 库存 | storage/inventory/* | 导入、库存/发运导出 |
| 资金 | fund/payment-order/*、account | 导出和代理 |
| 盘点 | storage/pd/import | 本地解析后转发 |
| 询价 | purchase/rfq/inquiry | 显式方法 |
| 通配 | allcarpart/:module/:controller/:action | 转到 index/$action |
显式 import/print/export 路由必须放在三段通配之前,否则可能被错误转发到通用 index。
41. 全车件请求上下文
代理 Header 由服务端 Session 生成:
{
"sid": "当前服务站",
"uid": "当前账号",
"user_name": "当前姓名"
}
这些字段不是前端可信入参。微服务必须只信任经过网关认证的 DGJ 请求,并对 Header 做来源验证。当前本地 Controller 把下游异常转换为 JSON code/message,但通常仍是 HTTP 200;前端要按业务码判断。
42. 全车件本地导入、打印和导出不是纯代理
部分 Controller 会:
- 读取上传 Excel。
- 在本地解析列、转换 SKU/单号。
- 调全车件微服务查询/导入。
- 用 DGJ View 生成打印页面或 PDF。
- 把微服务数据导出 Excel。
因此全车件改字段时要同时回归微服务 JSON、DGJ 导入模板、PHP 解析、打印 View 和导出列,不能只测通用 forward。
43. 新直连 BaseApiController 请求解析
BaseApiController 统一支持:
Content-Type: application/json:解析 raw body。- 其他 Content-Type:合并 GET、POST 和 urlencoded raw body。
Request-IdHeader:保存到 Controller 属性。ShipperHeader:保存货主标识。Inspire-Api-UserHeader:URL decode 后解析 JSON 身份。- 结构化日志:URI、Body、Header。
flowchart TD
A[HTTP请求] --> B{Content-Type是JSON}
B -->|是| C[json_decode raw stream]
B -->|否| D[GET+POST+parse_str body]
C --> E[requestData]
D --> E
E --> F[读取Request-Id/Shipper/Headers]
F --> G[InnerToken条件校验]
G --> H[解析Inspire-Api-User]
H --> I[具体Controller方法]
44. BaseApiController 内部认证边界
代码逻辑是:当模块不在少数例外列表、且 InnerTokenAuth::isInnerServer() 判定为内部服务时,才读取 body inner_token;而 URI 第一段为 inner 时又跳过该 token 校验。
这说明认证边界依赖:
isInnerServer()的网络/请求判断。- URI 分段。
- 上游网关对
inner路径的保护。 Inspire-Api-UserHeader 的可信注入。
不能只看到 BaseApiController 就认为所有内部接口都校验 token。每个新入口必须画清“谁能到达 URL、谁写 Header、应用校验什么”。
45. v2 并不等于 OpenAPI v2
当前 controllers/v2 只有 Application、Desktop、Demo、Resource、Report、Right 等 PC 功能,继承普通 BaseController,默认执行 Session、账号可用性、维护状态和菜单权限检查。
flowchart LR
A["/v2/Right 等"] --> B["BaseController 默认构造"]
B --> C["login_session"]
C --> D["账号、维护、菜单校验"]
D --> E["v2 Service"]
它是 PC 代码版本/资源权限演进,不应被外部调用方当成稳定的开放平台版本协议。
46. 鉴权能力对照矩阵
| 入口 | Session | 签名 | requestId 防重 | Redis Token | 功能权限 | 主要缺口 |
|---|---|---|---|---|---|---|
| 老 OpenAPI | 否 | 未见 | 有,条件启用 | 否 | 未见 | 依赖外部网络边界 |
| APP | 条件有 | 有但存在旁路 | 签名级无 nonce | 否 | 业务内 | 旁路、签名生成、设备校验禁用 |
| 老 SAAS | 否 | 未见 | 仅业务字段 | 否 | 未见 | 入口鉴权和统一幂等 |
| PointMall | 否 | 有 | 未见 | 否 | 未见 | 可映射范围和签名回显 |
| 令牌 OpenSaaS | 否 | 否 | nostr 用于提交 | 有 | 客户上下文 | Token 日志与重放 |
| 全车件 | 有 | 服务间另行确认 | 微服务负责 | 否 | 开通标志+权限 | 下游 Header 信任 |
| BaseApi/inner | 否 | 入口自定 | Request-Id仅保存 | Inner Token条件校验 | 入口自定 | 路径和网关边界不统一 |
| PC v2 | 有 | 否 | 业务自定 | 否 | 菜单/资源 | 不是开放版本合同 |
47. 响应合同对照
| 家族 | 成功判断 | 失败判断 | HTTP 状态 | 兼容风险 |
|---|---|---|---|---|
| 老 OpenAPI/SAAS | status=success | status=error,error_code | 多为 200 | 字段多、原 data/params 兼容 |
| APP | splash/各 Service 自定义 | 文本、JSON 或异常输出 | 不统一 | 老客户端强依赖文案 |
| OpenSaaS/inner | R::success | R::error | 取决于 R 实现 | 与老 envelope 不同 |
| 全车件 | 下游 code/message/data | 本地异常转 code/message | 多为 200 | 下游可能使用 0 或 200 表示成功 |
| PC v2 | 页面/splash/JSON | 登录重定向或业务错误 | 页面语义 | 不适合外部系统 |
48. 字段兼容规则
- 不删除旧字段;先新增可选字段并保持默认值。
- 不把
transCode和tradeCode统一改名到原路径。 - 不改变
data是 JSON 字符串还是对象的历史形态。 - 不改变
qty正负方向、金额单位和字符串/数字类型。 - 不改变来源单号的大小写、前缀和前导零。
- 不复用旧字段表达新语义;新增明确字段和版本。
- 返回新增字段不能破坏老客户端严格反序列化。
flowchart LR
A[旧请求v1] --> C[兼容适配层]
B[新请求v2] --> C
C --> D[规范领域Command]
D --> E[业务Service]
E --> F[规范领域Result]
F --> G[旧响应适配]
F --> H[新响应适配]
49. 状态和错误码兼容规则
- 外部状态必须先映射到本地状态机,不能直接写任意
billStatus。 - 对已完成/已关闭终态的重复更新,要返回幂等成功或明确不可变错误。
- 同一个错误码不能在不同版本改变语义。
- 新错误先使用新增 code,旧路径保留原 message。
- “接口已弃用但成功”必须增加可观测告警,避免静默丢业务。
- 批量部分成功必须返回逐行结果,不能只给总成功。
50. 推荐迁移架构
flowchart TB
L[Legacy Controllers] --> AD[Anti-Corruption Adapters]
N[Versioned /api/v2] --> DTO[Typed Request DTO]
AD --> DTO
DTO --> AUTH[统一认证/租户/幂等]
AUTH --> CMD[Domain Command]
CMD --> DS[Domain Service]
DS --> OB[Outbox/Audit]
DS --> RES[Domain Result]
RES --> LR[Legacy Response Adapter]
RES --> NR[Versioned Response]
老入口不直接重写业务,而是只做字段、状态、错误码和身份适配;新旧入口最终调用同一个领域 Service。这样才能既修复安全和一致性,又不复制两套订单逻辑。
51. 接口下线流程
stateDiagram-v2
[*] --> 存量
存量 --> 已标记弃用: 文档+响应字段+监控
已标记弃用 --> 双轨迁移: 新旧入口并行
双轨迁移 --> 只读兼容: 禁止新写,保留查询
只读兼容 --> 无流量: 调用量连续为0
无流量 --> 下线: 路由/映射/类删除
双轨迁移 --> 回退: 新版异常
每一步都要有调用方、流量、替代接口、截止时间和回滚负责人。仅在代码注释写“弃用”不算完成迁移。
52. 映射配置自动审计
建议把以下检查加入发布前任务:
配置键是否重复
映射文件是否存在
文件名和类名大小写是否匹配
类是否有 public service 方法
同一文件是否被多个交易码引用
标记弃用的交易码是否还有流量
缺失映射是否有明确替代入口
flowchart LR
A[apis/appapis/SaasApis] --> B[解析映射]
B --> C[检查目标文件]
C --> D[反射类和service方法]
D --> E[重复键/孤立文件/缺失文件报告]
E --> F[与线上访问日志合并]
F --> G[阻断高风险发布]
53. SOP:交易码返回无效或类不存在
- 确认请求使用
transCode还是tradeCode。 - 确认进入
/Openapi、/OpenSaasApi、/Carsteward还是/PointMall。 - 在对应配置文件查交易码,不能只全局搜同名方法。
- 把映射的
.替换为/,检查目标文件大小写。 - 检查末段类名和
service()方法。 - 查线上部署包是否与当前仓库一致。
- 查调用方是否仍在使用已经注释“备用/弃用”的 code。
rg -n '"shmstockOut"|"appsale2StockOut"|目标交易码' \
application/config/apis.php application/config/SaasApis.php application/config/appapis.php
rg -n 'class StockOut|public function service' application/service/api
54. SOP:APP 签名或登录失败
flowchart TD
A[APP调用失败] --> B{错误阶段}
B -->|缺sign/data/code| C[核对envelope]
B -->|签名不一致| D[核对data原始字符串/类型/精度]
B -->|Login failure| E[查Session和Cookie]
B -->|Compulsory offline| F[查黑名单/强制重登]
B -->|接口类不存在| G[查appapis和文件]
D --> H[确认客户端版本与签名算法版本]
E --> I[确认是否NO_LOGIN或reqCode旁路]
排查时禁止把服务端共享密钥或完整签名请求发到聊天、文档或工单。只比较脱敏后的排序字段、类型、是否为空和客户端/服务端摘要。
55. SOP:requestId 重复、超时或结果不确定
- 查 Redis 锁是否仍在 10 分钟窗口。
- 查 Mongo
dgj_api_log的requestId/status/createTime/modifyTime。 - 用业务来源单号查 MySQL 最终事实。
- status=1 但客户端无结果时,不要换新 requestId 重建业务。
- status=0 且业务表无事实时,可复用同 requestId 重试。
- status=0 但业务表已有部分事实时,进入专项人工核对。
flowchart TD
A[调用超时] --> B[查Mongo status]
B -->|1| C[查业务事实并视为已处理]
B -->|0/无记录| D[查业务来源单号]
D -->|无事实| E[同requestId安全重试]
D -->|完整事实| C
D -->|部分事实| F[停止自动重试,逐行核对]
56. SOP:SAAS 订单重复或金额不一致
SELECT id, sid, billNo, srcOrderNo, srcOrderId, billStatus,
totalQty, totalAmount, amount, arrears, createTime
FROM t_scm_sa_order_0_:sid_mod_16
WHERE sid = :sid AND srcOrderNo = :src_order_no;
明细表实际分片名按第 10、15 篇确认。排查:
- 客户编码是否绑定唯一服务站。
- 同
sid+srcOrderNo是否多行。 - 主单总额是否等于明细计算金额。
- 明细
qty是否为负数。 srcOrderId/srcOrderEntryId是否完整。- 事务提交后提醒 MQ 失败不能作为重建理由。
57. SOP:全车件代理失败
- 确认 PC Session、账号状态和菜单权限。
- 查服务站
isAllCarPart和ALL_CAR_PART数据权限。 - 确认命中显式路由还是三段通配。
- 查本地是否先做 Excel/打印转换。
- 查转发 Header 中的 sid/uid 是否来自正确 Session。
- 查
MICRO_URL下游响应code/message和超时。 - 对导入/导出同时检查文件、对象存储和模板列。
58. SOP:inner/直连接口身份错误
- 先确认 Controller 的父类,不要假定所有
/inner都一样。 - 查
BaseApiController是否实际执行InnerTokenAuth。 - 查 URI 第一段、模块例外列表和
isInnerServer()结果。 - 确认
Inspire-Api-User由哪个可信网关注入。 - 查
Request-Id是否贯穿日志,但不要认为它自动幂等。 - 查目标 Service 是否自行校验 sid、uid、货主和权限。
59. 常用只读查询和日志定位
SELECT id, source_type, status, time
FROM t_sys_apis_log4
WHERE source_type = :req_code
ORDER BY id DESC
LIMIT 100;
实际字段以生产 DDL 为准,优先查日志平台而不是扫描全表中的大 JSON。
rg -n "class OpenService|class OpenAppService|class OpenSaasService|class OpenPmService" application/service
rg -n "transCode|tradeCode|requestId|setRequestLock|dgj_api_log" \
application/controllers application/service application/core
rg -n "verify.*yes|checkDevice|NO_LOGIN_CHECK_APIS|appSignKey" application/service/OpenAppService.php
rg -n "allcarpart" application/config/routes.php application/controllers/allcarpart
rg -n "extends BaseApiController|extends OpenSaasController|extends BaseController" \
application/controllers/inner application/controllers/opensaas application/controllers/v2
rg -n "srcOrderNo|srcOrderEntryId|shmOrderCreate|shmstockOut" \
application/service/api application/models
60. 安全和兼容风险登记册
| 编号 | 风险 | 影响 | 建议 |
|---|---|---|---|
| R48-01 | 老 OpenAPI 代码内未见鉴权 | 外部可伪造业务请求 | 确认网关后增加应用级调用方认证 |
| R48-02 | 老 SAAS 同样无签名/requestId | 重放和伪造 | mTLS/HMAC+业务幂等 |
| R48-03 | APP verify=yes 旁路 | 绕过签名 | 立即下线并审计访问量 |
| R48-04 | APP 错误回显计算签名 | 降低攻击成本 | 只返回稳定错误码 |
| R48-05 | Carsteward/build 提供签名生成 | 形成签名 oracle | 从生产路由移除/仅本地 CLI |
| R48-06 | 共享密钥硬编码 | 无法轮换和隔离调用方 | 密钥中心+key id+版本轮换 |
| R48-07 | 签名不含 envelope/数组 | 字段可被替换或重放 | 新版 HMAC 覆盖 canonical body |
| R48-08 | reqCode=qcj 跳过登录 | 来源伪造导致越权 | 网关固定来源+交易码白名单 |
| R48-09 | checkDevice 提前 return | 单设备策略失效 | 先补数据再灰度启用 |
| R48-10 | APP 捕获异常 print_r | 非 JSON/信息泄漏 | 统一异常响应和日志 |
| R48-11 | 配置缺失目标 43 项 | 运行时类不存在 | 映射 lint+流量治理 |
| R48-12 | 重复映射键静默覆盖 | 配置漂移 | 解析源文件检测重复键 |
| R48-13 | 文件/类大小写依赖 | Linux 发布失败 | CI 在 Linux 检查 |
| R48-14 | requestId 锁仅 10 分钟 | 过期后可重入 | 业务唯一键+持久幂等结果 |
| R48-15 | 重复成功不返回原始结果 | 调用方状态不确定 | 保存并重放首次响应摘要 |
| R48-16 | Mongo 记录完整请求 | 敏感数据和容量 | 字段脱敏、TTL/归档 |
| R48-17 | DB 接口日志部分注释、部分仍写 | 排查口径不一致 | 统一结构化日志平台 |
| R48-18 | 多数错误仍 HTTP 200 | 网关/监控误判 | 新版本标准化,旧路径兼容 |
| R48-19 | 弃用接口返回 success | 调用方静默丢业务 | 机器可识别弃用状态和告警 |
| R48-20 | 批量接口可部分成功 | 整批重放重复 | 返回逐行结果+行级幂等 |
| R48-21 | SAAS 主明细金额来源不同 | 金额不一致 | 服务端统一重算并校验 |
| R48-22 | SAAS 来源单唯一索引未知 | 并发重复销售单 | 生产 DDL 确认并补唯一键 |
| R48-23 | OpenSaaS Token 写日志 | Token 泄漏 | 日志脱敏、短期单用途 |
| R48-24 | 全车件信任转发 Header | 伪造租户上下文 | 服务间认证并签名 Header |
| R48-25 | BaseApi 记录完整 Header | 内部凭据泄漏日志 | Header allowlist/redaction |
| R48-26 | /inner 路径跳过 token | 依赖隐含网络边界 | 明确网关策略和双层认证 |
| R48-27 | v2 名称易被误当开放版本 | 错误集成 | 文档和路由命名分层 |
| R48-28 | 坐标签名精度兼容可能未触发 | 配送接口签名失败 | 用字符串坐标和契约测试 |
61. 回归测试清单
61.1 老 OpenAPI
- 6 个通用缺参错误码和无效交易码。
- 目标文件不存在、类名大小写、缺
service()。 - requestId 首次、并发重复、成功后重复、失败后重试、锁超时。
- Mongo status 0/1 和业务表事实一致。
reqCode=KW与其他来源的中文编码。- 弃用接口保持旧结构但产生弃用告警。
shmstockOutSH/TS、同批重复、部分成功、0 数量、事务失败。
61.2 APP
- NO_LOGIN 交易码与普通交易码。
- 签名字段顺序、大小写、空值、数字、浮点、嵌套数组。
- Session 失效、黑名单、强制重登、服务站状态异常。
- 所有旁路字段在生产无效。
- 服务端注入 sid/uid 覆盖客户端同名字段。
- 老客户端错误 JSON 和新客户端兼容。
- 39 个缺失映射逐项有流量或替代结论。
61.3 SAAS 和 OpenSaaS
- 客户未注册、未绑定、重复来源单、商品缺失、金额不一致。
- 同
srcOrderNo并发只产生一张销售单。 - 事务成功 MQ 失败不重复建单。
pr缺失、失效、错误客户、过期和重复使用。nostr重复提交、支付缓存 30 分钟边界、支付状态轮询。
61.4 全车件、inner 和 v2
- 未登录、未开通、无数据权限、正常代理。
- 显式 route 优先级和三段通配。
- 导入、打印、导出以及下游 0/200 两种成功码。
- JSON/form 两种 BaseApi 请求解析。
- InnerToken、URI、网关注入用户和 Request-Id。
- v2 PC Session、菜单、总分店切换,不作为外部 API 暴露。
62. 证据来源
| 主题 | 当前代码证据 |
|---|---|
| 老 OpenAPI Controller/分发/Mongo | application/controllers/Openapi.php、application/service/OpenService.php |
| APP 入口、签名、登录和分发 | application/controllers/Carsteward.php、application/service/OpenAppService.php |
| 老 SAAS | application/controllers/OpenSaasApi.php、application/service/OpenSaasService.php |
| 积分商城 | application/controllers/PointMall.php、application/service/OpenPmService.php |
| 交易码配置 | application/config/apis.php、appapis.php、SaasApis.php |
| 统一响应、锁和用户注入 | application/core/BaseService.php |
| PC/Direct API 基类 | application/core/BaseController.php、BaseApiController |
| 老采购出库代表链 | application/service/api/invoice/StockOut.php |
| SAAS 销售创建代表链 | application/service/api/saas/OrderCreate.php |
| 令牌 OpenSaaS | application/controllers/opensaas/Order.php、OpenSaasController |
| 全车件路由和代理 | application/config/routes.php、application/controllers/allcarpart/Controller.php |
| 全车件本地转换 | application/controllers/allcarpart/* |
| PC v2 | application/controllers/v2/*、application/service/v2/* |
| 日志表常量 | application/config/tables.php |
63. 待环境确认
/Openapi、/OpenSaasApi、/Carsteward/build、/PointMall的生产公网可达性、网关认证、IP 白名单和调用方清单。- APP
verify=yes、reqCode=qcj和签名生成入口是否仍有真实流量。 - 共享签名密钥的调用方数量、轮换机制和客户端最低版本。
- 39 个 APP、2 个老 OpenAPI、2 个 SAAS 缺失映射的线上访问量与替代入口。
- Redis requestId 锁前缀、Mongo collection 索引、唯一约束、TTL 和容量。
t_sys_apis_log1-4的生产 DDL、写入量、保留期和查询索引。- 老调用方对 HTTP 状态、
status/error_code/message/result/params的实际判断方式。 - SAAS 销售表
(sid,srcOrderNo)和采购出库(billNo,boxNo,skuId)的生产唯一索引。 - OpenSaaS
prToken 的创建入口、TTL、单用途/撤销策略和日志脱敏。 - 全车件微服务的服务间认证、Header 信任、成功码和幂等合同。
InnerTokenAuth::isInnerServer()的真实网络判定以及/inner路由的网关保护。- 所有旧接口的责任人、替代版本、迁移截止日和回滚方案。
64. 修改后的最小发布验证
- 用不存在的交易码验证错误码和日志,不执行写业务。
- 选一个只读老 OpenAPI,验证 envelope、requestId 首次/重复和 Mongo 状态。
- 选一个 APP 只读接口,验证签名、Session 和服务端用户注入。
- 验证所有签名旁路和签名生成入口在目标环境不可用。
- 选一个 SAAS 只读接口和一笔测试来源单,验证来源单幂等。
- 验证全车件未开通、已开通和下游失败三条分支。
- 验证一个 BaseApi JSON 和 form 请求,确认 Header 脱敏日志和身份校验。
- 对照配置自动审计报告,确保本次改动没有新增缺失文件、重复键和类名大小写问题。
- 最后回归旧客户端/旧调用方原响应字段、错误码、状态和编码,确认新安全层没有改变其合法请求语义。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 老 OpenAPI | Openapi.php + apis.php | appId、timestamp、sign、API code、外部单号/参数 | Openapi Controller | OpenService -> service/api/* | API code + source order | 老合同转换为内部业务动作 |
| App/车管家 | Carsteward.php + appapis.php | App 签名、用户/站点、API code | Carsteward Controller | OpenAppService | app code + business ID | App 调用领域 Service |
| OpenSaaS/积分商城 | OpenSaasApi/PointMall | tenant/app、签名、来源单 | 对应 Controller | OpenSaas/OpenPm Service | tenant+source order | SaaS/积分业务转换为内部单据 |
| 全车件转发 | /allcarpart/*/opensaas/Order | route、来源单、业务 payload | Allcarpart/OpenSaas Controller | MICRO_URL Provider | request+source order | 请求代理到微服务并返回兼容响应 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 路由/API code | Web/Openapi 日志 | request_id、URI、API code、appId/tenant | 命中预期 service/api 类方法 | E10001/路由未配置、方法不兼容 | API code 映射服务方法 | | 鉴权/解析 | OpenService | appId、timestamp、nonce/sign 结果、source order | 鉴权成功,参数按旧合同归一 | E10002-E10008、JSON/form 编码差异 | 标准参数进入领域 Service | | 业务处理 | service/api/*/领域 Service | 类方法、sid、source/internal bill | commit 并返回旧格式成功码 | E50001、部分写入、重复来源 | 来源单映射内部单号 | | 代理下游 | MICRO Provider/OpenSaas | external request ID、source order、HTTP/业务码 | 下游成功且响应适配 | timeout、HTTP 200 业务失败 | 两端 request/source ID |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 入口映射 | apis.php/appapis.php/routes | 事务外 | path/API code/method | 无 | old code -> service class/method | 配置行、request ID |
| 鉴权上下文 | OpenService/OpenAppService | 事务外 | app/tenant、签名、时间窗、站点 | API 请求日志(脱敏) | 合法请求补入 sid/user/source;失败 0 业务写入 | 错误码、上下文快照 |
| 参数兼容 | service/api/* | 事务外 | 旧字段名/类型/编码 | 无 | old payload -> canonical DTO;默认值保留旧语义 | 入参映射表、回归样例 |
| 领域落库 | Purchase/Sale/StockOut 等 Service | 本地事务 | 来源唯一性、状态、数量 | 业务主明细/库存/资金 | insert/update old -> new;source order 建关系 | source/internal bill、流水 |
| 响应/异步 | Openapi Controller/MqSer/Provider | commit 后 | 业务结果 | 旧 JSON 响应、MQ/下游 | 保持原字段/错误码/编码;异步结果另行跟踪 | 响应快照、message/external request ID |
子模块追踪:legacy-openapi 老 OpenAPI 动态分发与幂等
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 动态分发 | 合作方提交 transCode | appId、timestamp、requestId、sourceOrderNo | application/controllers/Openapi.php -> application/config/apis.php -> application/service/OpenService.php | 签名/时间窗、code 映射、来源唯一性和请求记录 | 鉴权/适配阶段不写;目标领域本地事务 old -> new | request ID + transCode + source/internal billNo | code/签名失败零业务写入;超时按来源单回查 |
| 幂等重放 | 同来源/请求再次调用 | requestId、sourceOrderNo、transCode | application/service/OpenService.php | 已有映射、主业务和库存/资金副作用 | 已完成 new -> new、新增单据/流水 0 | old/new request IDs + source/internal | requestId 语义按代码合同;只补响应/异步段 |
子模块追踪:app-api App API 签名、登录与映射
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| code 映射 | App 提交 API/trade code | code、signature/token、user/sid、business key | application/config/appapis.php -> application/service/OpenAppService.php | code 类映射、登录上下文、签名和参数 | 路由鉴权不写;目标 API 领域本地事务 old -> new | request ID + code + user/sid + bill/source | 未登录/签名/code 不存在零业务写入 |
| 兼容回查 | App 成功/失败与 PC 不一致 | code、service class、response | application/config/appapis.php | 实际类文件、上下文注入和业务状态 | 查询只读;响应阶段 DB 不变 | request ID + code/class + response/business row | 映射改动扫描所有客户端版本;超时先查业务事实 |
子模块追踪:legacy-saas 老 SAAS 映射与销售建单
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| SAAS 分发 | 老 SAAS code 创建销售等业务 | saas code、tenant/sid、sourceOrderNo | application/config/SaasApis.php -> application/service/api/saas/OrderCreate.php | tenant 映射、商品客户、来源唯一性和旧字段 | 销售本地事务 insert 主明细/来源关系,none -> created | request ID + saas code + source/sale billNo | 重复来源返回既有单;字段缺失零写入 |
| 下游同步 | 销售/出库后回传 SAAS | source/internal billNo、event | application/Services/Mq/MqSer.php | 已提交本地事实、旧 payload 合同和发送记录 | commit 后事务外发 MQ,本地核心 DB 不变 | both billNos + routing/message result | 发送失败只补消息;不重复建销售/扣库存 |
子模块追踪:open-saas 令牌式 OpenSaaS 与 VIP 下单
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 令牌鉴权 | OpenSaaS API 请求 | token hash、tenant/sid、path、sourceOrderNo | application/controllers/OpenSaasApi.php -> application/service/OpenSaasService.php | token 状态/范围、租户站点、route 和时间 | 鉴权查询只读;失败 0 业务写入 | request ID + token hash + tenant/sid + path | token 不入日志;过期/越权明确错误码 |
| VIP 下单 | OpenSaaS VIP 创建订单 | sourceOrderNo、VIP/customer、items | application/controllers/opensaas/Order.php | VIP 资格、商品价库存、来源唯一性和地址 | 订单本地事务 insert 主明细/关系 none -> created | request ID + source/internal order + customer | timeout 按 sourceOrderNo 回查;重复 0 新单 |
子模块追踪:point-mall 积分商城映射入口
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 积分请求 | 积分商城 code/path 调用 | app/source order、user/sid、points/items | application/controllers/PointMall.php -> application/service/OpenPmService.php | 鉴权、积分/商品合同、来源唯一性和映射 | 适配不写;目标领域本地事务创建订单/关系 none -> created | request ID + source/internal + user/sid | 外部积分扣减与本地订单非原子,timeout 查两端终态 |
| 取消补偿 | 建单失败/取消返积分 | source/internal order、points transaction | application/service/OpenPmService.php | 本地订单终态、外部积分扣/返和已有补偿 | 本地补偿事务状态/关系 old -> expected;外部返还事务外 | both orders + points transaction + result | 只补失败端;同原交易键返还一次 |
子模块追踪:allcarpart-proxy 全车件代理与本地能力
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 路由代理 | allcarpart 显式 URL | path/method、auth、external request/order | application/config/routes.php -> application/controllers/allcarpart/Controller.php | 路由目标、鉴权、代理/本地分支和参数 | 纯代理本地 DB 不写;本地能力走领域事务 old -> new | request ID + route + external/local order | timeout 按外部业务键回查;不因代理失败切本地重复执行 |
| 本地车管家 | 路由到本地询价/订单能力 | sourceOrderNo、VIN/SKU、sid | application/controllers/Carsteward.php | 来源映射、商品/报价和当前状态 | 本地领域事务 insert/update,保存 external/internal 关系 | request ID + source/internal + method | 外部合同需联调;敏感 VIN 脱敏,来源幂等 |
子模块追踪:base-api BaseApiController 直连接口
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 直接请求 | URL 直接命中 API Controller | method/path、headers、sid、business key | application/core/BaseService.php -> application/core/BaseController.php | 内部身份、method、JSON/form、用户站点和权限 | 解析鉴权不写;领域 Service 本地事务 old -> new | request ID + path/method + sid/billNo | 与 code 网关协议分开;401/403/校验失败零写入 |
| 结果回查 | 直连接口返回异常/超时 | request ID、business key | application/core/BaseService.php | 应用日志、主业务、异步消息和响应合同 | 查询只读;响应/异步阶段 DB 不变 | request ID + business row + response/message | 先查是否 commit;只清理/补偿本次业务段 |
子模块追踪:compat-response 旧响应、错误码与字段兼容
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 响应适配 | 领域结果返回老客户端 | API/code/version、result/error | application/controllers/Openapi.php -> application/service/OpenService.php | 旧 success/code/message/data、字段类型/编码和调用方版本 | 响应转换查询/内存操作 不写 DB;保留旧字段默认值 | request ID + API/version + response snapshot | HTTP 200 与业务失败分开;错误码变更需兼容映射 |
| 合同回归 | 新旧请求/响应对照 | fixture ID、old/new payload | application/service/api/invoice/StockOut.php | 参数别名、默认值、数值/字符串和副作用 | 对照测试仅测试数据事务;同输入最终业务事实一致 | fixture + old/new response + DB diff | 字段可新增不可破坏旧必填/语义;日志不含敏感 payload |
子模块追踪:api-migration 接口迁移、审计与下线
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 调用审计 | 计划从旧 API 迁新入口 | old code/path、caller/version、time window | application/config/apis.php、application/config/appapis.php、application/config/routes.php | 全部静态映射、访问日志、动态调用方和业务合同 | 审计查询只读 不写;形成迁移矩阵和最后调用时间 | old/new route + caller/version + request count | 未证明零调用不下线;环境访问日志必须实际验证 |
| 双跑下线 | 灰度切新 API、禁用旧映射 | migration ID、feature/caller scope | application/service/OpenAppService.php | 新旧响应/副作用、幂等键、失败率和回滚开关 | 目标调用方路由 old -> new;同业务不得双写,旧入口最终明确废弃码 | migration + caller + old/new metrics/DB diff | 灰度异常回切;下线后保留可观测错误,不静默路由到错误业务 |