主题
08 — 待办与通知公告推送(第三方集成手册)
读者:第三方业务系统开发 / 联调人员
前提:已按 01-接入准备.md 拿到client_id、client_secret,并绑定system_code
字段级权威:事项 02、公告 07;运行时见 Knife4j/doc.html
设计背景(非必读):doc/36、doc/16
本文把两条入站管道写在一起,便于选型与一次性联调。
1. 先选型:走哪条管道?
| 你的业务 | 调用 | 用户在门户哪里看 |
|---|---|---|
| 审批待办、要办结/重开/改状态 | 事项 /api/open/matters | 个人「事项 / 待办」 |
| 流程抄送、少数人知会(跟某单绑定) | 事项(matterType=NOTICE 等 + parties) | 同上 |
| 放假/制度等广播公告(全员、按部门、用户列表) | 公告 /api/open/notices | 个人「通知公告」收件箱 |
| 需要管理端「已发公告」与撤回停投递 | 公告 | 同上 |
text
第三方系统
│
├─ HMAC ──► /api/open/matters/** → portal_matter* → /api/portal/matters
│
└─ HMAC ──► /api/open/notices/** → portal_notice* → /api/notice/inbox禁止:同一业务主键既推事项又推公告(用户会看到两条)。
2. 公共约定(两条管道相同)
2.1 鉴权头
| Header | 必填 | 说明 |
|---|---|---|
X-Client-Id | 是 | 门户下发的 client_id |
X-Timestamp | 是 | Unix 秒(与门户时钟偏差 ≤ 300 秒) |
X-Signature | 是 | HMAC-SHA256 的 Hex(大小写不敏感) |
X-Idempotency-Key | 否 | 事项记 portal_matter_push_log;公告记 portal_notice_push_log(管理端消息日志可查,非对接必调) |
2.2 签名
text
签名串 = clientId + "\n" + timestamp + "\n" + rawBody
签名 = Hex( HMAC-SHA256( client_secret, 签名串 ) )| 注意 | 说明 |
|---|---|
rawBody | 必须与 HTTP 实际发送的 JSON 字节完全一致(勿先对象再二次 JSON.stringify 改空格/字段序) |
system_code | 只认 Client 绑定,body 里带了也会被忽略 |
| TLS | 生产须 HTTPS;一期无应用层加密信封(L3) |
Node.js
js
const crypto = require('crypto');
function sign(clientId, secret, timestamp, rawBody) {
const payload = `${clientId}\n${timestamp}\n${rawBody}`;
return crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
}Java(Hutool)
java
String payload = clientId + "\n" + timestamp + "\n" + rawBody;
String signature = new HMac(HmacAlgorithm.HmacSHA256, secret.getBytes(UTF_8)).digestHex(payload);2.3 统一响应
json
{ "code": 0, "message": "success", "data": { }, "traceId": "..." }- 以
code === 0为成功(鉴权失败也常是 HTTP 200 +code=401/403)。 - 事项 / 公告:落库成功才
code=0,失败应重试。
2.4 幂等键
| 管道 | 幂等 |
|---|---|
| 事项 | system_code + externalId(UPSERT 可更新) |
| 公告 | system_code + externalId(仅首次 publish;再次 → 409) |
externalId 由你们生成并保持稳定(建议业务单号 / 公告号)。
3. 事项管道速查(/api/open/matters)
完整字段见 02-事项推送-HMAC.md。
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /upsert | 创建或更新 + 参与人 |
| POST | /status | 改办理态 / 第三方原文态 |
| POST | /reopen | 重开 |
| POST | /delete | 软删(仍是 POST,不是 HTTP DELETE) |
3.1 待办 UPSERT 最小示例
json
{
"matterType": "TODO",
"externalId": "WF-1001",
"title": "请假审批",
"bizStatus": "PENDING",
"pcUrl": "https://oa.example.com/wf/1001",
"parties": [
{ "username": "zhangsan", "partyRole": "ASSIGNEE" }
]
}partyRole:ASSIGNEE 承办 / CC 抄送 / RECIPIENT 知会。username 须为门户已存在登录名。
3.2 流程知会(仍走事项,不是公告)
json
{
"matterType": "NOTICE",
"externalId": "WF-1001-CC",
"title": "抄送:请假审批",
"parties": [
{ "username": "lisi", "partyRole": "RECIPIENT" }
]
}3.3 curl(事项)
bash
BASE="https://portal.example.com"
CLIENT_ID="demo-client"
SECRET="your-client-secret"
TS=$(date +%s)
BODY='{"matterType":"TODO","externalId":"WF-1001","title":"请假审批","bizStatus":"PENDING","parties":[{"username":"zhangsan","partyRole":"ASSIGNEE"}]}'
SIG=$(node -e "const c=require('crypto');const b=process.argv[1],id=process.argv[2],ts=process.argv[3],s=process.argv[4];console.log(c.createHmac('sha256',s).update(id+'\n'+ts+'\n'+b).digest('hex'))" "$BODY" "$CLIENT_ID" "$TS" "$SECRET")
curl -sS -X POST "$BASE/api/open/matters/upsert" \
-H "Content-Type: application/json" \
-H "X-Client-Id: $CLIENT_ID" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"4. 公告管道速查(/api/open/notices)
完整字段见 07-通知公告推送-HMAC.md。
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /publish | 首次发布;同键再发 409 |
| POST | /recall | 撤回(inbox 不再展示) |
4.1 指定用户
json
{
"externalId": "ANN-20260728-001",
"noticeType": "NOTICE",
"title": "系统维护通知",
"summary": "今晚 22:00–23:00",
"content": "详细说明…",
"pcUrl": "https://app.example.com/ann/1",
"audience": {
"mode": "USERS",
"usernames": ["zhangsan", "lisi"]
}
}4.2 按部门(含下级)
json
{
"externalId": "ANN-DEPT-001",
"title": "部门制度更新",
"audience": {
"mode": "DEPTS",
"deptCodes": ["D001"],
"includeSubDepts": true
}
}4.3 全员
json
{
"externalId": "ANN-ALL-001",
"title": "国庆放假安排",
"audience": { "mode": "ALL" }
}预估收件人 > 500 时门户异步投递:data.dispatchMode=ASYNC,可带 taskCode;用户仍从通知公告收件箱读取。
4.4 撤回
json
{ "externalId": "ANN-20260728-001" }4.5 curl(公告)
bash
BASE="https://portal.example.com"
CLIENT_ID="demo-client"
SECRET="your-client-secret"
TS=$(date +%s)
BODY='{"externalId":"ANN-1001","title":"放假通知","audience":{"mode":"USERS","usernames":["zhangsan"]}}'
SIG=$(node -e "const c=require('crypto');const b=process.argv[1],id=process.argv[2],ts=process.argv[3],s=process.argv[4];console.log(c.createHmac('sha256',s).update(id+'\n'+ts+'\n'+b).digest('hex'))" "$BODY" "$CLIENT_ID" "$TS" "$SECRET")
curl -sS -X POST "$BASE/api/open/notices/publish" \
-H "Content-Type: application/json" \
-H "X-Client-Id: $CLIENT_ID" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"成功时 data 示例:
json
{
"dispatchMode": "SYNC",
"noticeId": 1234567890123456789,
"noticeCode": "ANN-...",
"externalId": "ANN-1001",
"dispatchStatus": "SUCCESS",
"estimatedRecipients": 1
}5. 常见错误
code | 含义 | 处理 |
|---|---|---|
401 | 缺头 / 签名错 / 时间窗外 / 未知 Client | 对时钟、核对 rawBody、secret |
403 | Client 停用 / 无 secret / 未绑 system_code | 找门户管理员 |
400 | 参数非法、受众为空等 | 按字段表修正 |
404 | 事项/公告不存在 | 核对 externalId |
409 | 公告已发布过同键;或事项侧冲突 | 公告勿重复 publish;事项用 upsert/status |
6. 联调检查清单
- [ ] CONFIDENTIAL Client +
client_secret+ 已绑system_code+ 启用 - [ ] 本机与门户时间偏差 < 5 分钟
- [ ] HMAC 用原始 body 字符串签名,Content-Type=
application/json - [ ] 事项:门户用户能在「事项」看到 TODO;
username真实存在 - [ ] 公告:门户用户能在「通知公告」看到;看不到事项列表里
- [ ] 公告二次相同
externalId→409;recall后收件箱消失 - [ ] 不以 HTTP 状态码单独判成功,以
code===0为准
7. 文档索引
| 文档 | 用途 |
|---|---|
| 01-接入准备.md | Client / 响应 / 公共错误 |
| 02-事项推送-HMAC.md | 事项四接口字段级 |
| 07-通知公告推送-HMAC.md | 公告 publish/recall 字段级 |
| 本文 08 | 选型 + 双管道速查 + 示例 |
| README.md | 全部第三方能力总览 |
| sdk/README.md | SDK / Demo 下载索引 |
8. 下载 Demo
- HMAC Java / Bash:/downloads/hmac-java.zip 、/downloads/bash-hmac-scripts.zip