docs(hjc): 汇吉采标书购买领域词表、推送鉴权规范与ADR

This commit is contained in:
2026-09-08 20:09:41 +08:00
parent 79666898c5
commit 770aeff4d7
4 changed files with 183 additions and 0 deletions
@@ -0,0 +1,130 @@
# 汇吉采 → 一站式代理平台:标书订单推送接口鉴权规范
> 用途:本文档是**本平台(汇吉采标书购买平台,下称「官方网」)调用一站式代理平台接口**时的鉴权规范,用于对接一站式平台开发人员,请对方按此规则在服务端做校验。
> 目标接口:`POST /api/biz/createPurchaseDetails`(本平台推送购买记录与订单信息到一站式平台落盘)
---
## 1. 共享凭证(双方提前约定,不通过网络传输)
| 参数 | 值 | 说明 |
|---|---|---|
| `appKey` | `HJC_Official_Website` | 调用方标识,明文放在请求头 |
| `password` | `vQ8$kR3#mW6@xP2!nF` | **签名密钥(Secret**,仅双方服务端持有,**不放在请求头、不出现于任何请求/日志/报文** |
> ⚠️ 出于安全考虑,本规范**不把 `password` 明文放请求头**。原始需求中「把 password 明文字段加到请求头」的做法会泄露密钥,改为「用 password 参与签名、请求头只带签名结果」的等价且更安全的方案(见第 3 节)。如需严格沿用原始「明文 password 头」方案,见附录 A。
---
## 2. 请求头(请求方——官方网——每次调用携带 3 个)
| Header | 示例值 | 说明 |
|---|---|---|
| `appKey` | `HJC_Official_Website` | 明文 appKey(固定值) |
| `timestamp` | `202604080103` | 签名时间,格式 `yyyyMMddHHmm`(24 小时制,北京时区,精确到分钟) |
| `sign` | `9f7c…(32 位小写十六进制)` | 签名值,算法见第 3 节 |
另需:`Content-Type: application/json`
---
## 3. 签名算法
```
sign = MD5( appKey + password + timestamp )
```
- `+` 表示**字符串直接拼接**,中间无分隔符。
- `timestamp` 使用与请求头 `timestamp` **完全相同**的值。
- `MD5` 输出**小写** 32 位十六进制字符串。
步骤:
1. 取当前北京时间的分钟表示 `timestamp = yyyyMMddHHmm`(例:`2026-04-08 01:03``202604080103`)。
2. 拼串:`HJC_Official_Website` + `vQ8$kR3#mW6@xP2!nF` + `202604080103`
3. 对该字符串做 MD5,转小写,得到 `sign`
4.`appKey``timestamp``sign` 放入请求头发出。
---
## 4. 校验规则(一站式平台服务端)
收请求后按以下顺序校验,任一步不通过即拒绝(建议返回 `401` / `400`,并附错误码与说明,便于排查):
1. **校验 appKey**`appKey` 是否等于已登记的应用标识 `HJC_Official_Website`。不等 → 拒绝。
2. **校验 timestamp 时效(防重放)**`timestamp` 与**一站式服务器当前时间**之差是否在 **±10 分钟**以内(统一按北京时区、分钟精度)。超出窗口 → 拒绝,提示「时间戳失效」。
- 该窗口同时限制了重放窗口;如需更严可协商收紧到 ±3 分钟。
3. **校验签名**:用共享的 `password` 重算 `MD5(appKey + password + timestamp)`,与请求头 `sign` **不区分大小写**比较(统一小写后比较)。不一致 → 拒绝,提示「签名校验失败」。
4. 校验通过后,按请求体 `createPurchaseDetails` 的字段约定解析并落盘(业务字段见单独一节的「createPurchaseDetails 请求体字段表」)。
---
## 5. 幂等与重试约定(可选但强烈建议)
`createPurchaseDetails` 是「落盘」类写操作,本平台侧会做失败重试,因此建议一站式平台按业务主键幂等处理,避免重复落盘:
- 请求体需带唯一业务单号(如 `purchaseId` / `orderNo`),一站式平台以此作为去重键;**同一单号重复提交时应返回成功、不重复插入**(或返回「已存在」)。
- 本平台对失败的推送会按 `attempt` 递增重试(建议:1/2/4/8 分钟间隔),重试时 `timestamp/sign` 需要**重新计算**(每次请求都要以新时间重签)。
---
## 6. 双端示例
**官方网(请求方)**——以 Python 为例:
```python
import hashlib, time, requests
app_key = "HJC_Official_Website"
password = "vQ8$kR3#mW6@xP2!nF"
ts = time.strftime("%Y%m%d%H%M") # 202604080103
sign = hashlib.md5((app_key + password + ts).encode("utf-8")).hexdigest()
resp = requests.post(
"https://<一站式域名>/api/biz/createPurchaseDetails",
json={ ... 业务字段 ... },
headers={
"appKey": app_key,
"timestamp": ts,
"sign": sign,
"Content-Type": "application/json",
},
timeout=15,
)
```
**一站式(校验方)**——以 Java 为例:
```java
String appKey = req.getHeader("appKey");
String ts = req.getHeader("timestamp");
String sign = req.getHeader("sign");
if (!"HJC_Official_Website".equals(appKey)) return 401; // 1
if (Math.abs(System.currentTimeMillis() / 60000 - parseMinute(ts)) > 10)
return 401; // 2
String expect = md5("HJC_Official_Website" + SECRET + ts); // 3
if (!expect.equalsIgnoreCase(sign)) return 401;
// 4. 解析 body,按 createPurchaseDetails 字段落盘(按 orderNo 幂等)
```
---
## 附录 A:若一站式平台坚持「明文 password 头」的原始方案
原始需求表述为「请求头添加 appKey、passwordappKey+年月日时分、password+年月日时分,MD5 加密」。若对方已按此固定算法实现,则采用:
- 请求头:`appKey=HJC_Official_Website``password=vQ8$kR3#mW6@xP2!nF``sign=MD5(password + yyyyMMddHHmm)`(小写)。
- 校验:`password` 需与约定值一致,且 `MD5(password + timestamp) == sign`,同时 `timestamp` 在 ±10 分钟内。
> 该方案会把 `password` 以明文形式暴露在报文中,安全性差;**仅作兼容退路,不作为默认推荐**。若对方无法确认固定算法,请首选第 1–6 节的方案。
---
## 附:需要一站式平台确认/提供的信息
1. 上述 `sign` 拼接顺序是否与合作方既有算法一致(`appKey+password+timestamp`)。
2. 时间戳格式 `yyyyMMddHHmm` 与北京时区是否认可。
3. 校验窗口 ±10 分钟是否可接受。
4. `createPurchaseDetails` 请求体字段表(本平台另一份文档,见需求/接口清单)的具体入参名与必填项,需对方给出或双方对齐,以免字段名不一致导致落盘失败。