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
+47
View File
@@ -0,0 +1,47 @@
# 汇吉采标书购买平台
汇吉采标书购买平台是一站式代理平台的交易出口:企业账号在此注册、通过资质审核后购买标书,平台内完成订单与支付,并把购买记录推送回一站式平台。
## 核心实体
**项目 (Project)**:
一条被浏览/售卖的采购项目,是平台内容的主体。携带项目编号、名称/分类、招标人、中标公司、中标金额与中标公告信息。
_Avoid_: 标书项目, tender
**标书 (BidDocument)**:
挂在项目下、可购买下载的标书。一个项目当前对应一本标书,但按未来「一个项目多本标书」预留 1:N。携带标书价格(信息服务费)、标书附件、开售/停售时间与售卖方式。
_Avoid_: tender, 招标文件
**企业账号 (Enterprise Account)**:
买家账号,用企业名称+密码登录,须通过资质审核才能购买标书。
_Avoid_: 用户, 会员, buyer
**资质认证 (Qualification)**:
企业注册后提交、由平台审核的资质材料,状态为 待审核/已通过/已驳回(含驳回原因)。未通过不可下单。
_Avoid_: 认证
**订单 (Order)**:
企业购买标书的记录,含标书信息/数量/金额/购买企业,需支付后完成。
_Avoid_: 交易, purchase
**信息服务费 (Service Fee)**:
购买标书支付的金额,即「标书价格」。平台对每一本标书设定。
_Avoid_: 标书价格, 标书费
## 与一站式平台的关系
**一站式代理平台 (One-stop Platform)**:
上游系统,向本平台推送中标公告数据,并接收本平台推送的购买记录/订单落盘。
_Avoid_: 甲方, 上游
**中标公告 (Award Announcement)**:
项目流程走到「发布中标公告」时由一站式平台推送的公告数据,含招标人/中标公司/中标金额/公告正文/公告附件。
_Avoid_: 公告, bulletin
**售卖方式 (Selling Method)**:
标书的售卖渠道标识:1 公司财务 / 2 公众号 / 3 交易中心 / 4 政采云。
_Avoid_: 渠道
**推送 (Push)**:
本平台与一站式平台之间交换数据的动作:接收中标公告(入),并在订单支付成功后推送购买记录(出)。
_Avoid_: 同步, 对接
+3
View File
@@ -0,0 +1,3 @@
# 一站式推送鉴权:appKey + timestamp + signpassword 不进报文
汇吉采 → 一站式平台的订单推送采用 `appKey + timestamp + sign` 请求头鉴权,`sign = MD5(appKey + password + timestamp)`(小写),`password` 只作为密钥参与签名、不放入任何报文字段;一站式侧校验 appKey 匹配、timestamp 与本地时间差 ≤ ±10 分钟(防重放)、以及重算 MD5 与 sign 不区分大小写比对。原始需求文字要求把 `password` 明文加入请求头,会泄露共享密钥,本方案保留其 appKey/password/年月日时分/MD5 全部要素,仅把 password 降级为纯签名密钥,是相对原始字面的有意偏离;若一站式平台已按原始明文方案实现,兼容退路见 `docs/一站式平台推送-接口鉴权规范.md` 附录 A。理由:这是对外契约,改动成本高,且偏离点是刻意且必须记录,否则后人会「修复」回明文方案。
@@ -0,0 +1,3 @@
# 项目与标书建模为两个实体,关联 1 项目:N 标书
项目(Project)与标书(BidDocument)建为两个独立实体,标书持有项目外键,关联为 1 项目:N 标书。当前一对一,但需求明确未来可能出现一个项目挂多本标书,故标书字段不并入项目,而是独立成表。一站式推送的公告类字段(公告标题/招标人/中标公司/中标金额/公告正文/公告附件)落项目,销售类字段(标书价格/附件/开售/停售/售卖方式/needSell)落标书,二者都由「推送入库 + 管理后台手工增删改查」双路填充。理由:在项目/标书需求仍模糊时避免强行锁死单一结构,同时为「一项目多标书」预留扩展而不必迁移主数据。
@@ -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` 请求体字段表(本平台另一份文档,见需求/接口清单)的具体入参名与必填项,需对方给出或双方对齐,以免字段名不一致导致落盘失败。