新增 POST /api/aihr/org/sync,从开放组织系统 /open/v1/sync/snapshot 拉取公司/部门/员工快照刷新 aihr_org_snapshot;支持 dryRun 预检、 Bearer/client-credentials 鉴权与可选 HMAC 签名;外部未配置时保留 SQL seed。附接口契约设计 v1 与 API/DEV_SETUP 配置说明。
1701 lines
37 KiB
Markdown
1701 lines
37 KiB
Markdown
# 开放平台组织与预算同步 API 设计方案 v1
|
||
|
||
## 1. 文档目标
|
||
|
||
本文档用于定义本系统对外开放的组织与预算同步方案,覆盖以下对象:
|
||
|
||
- 集团
|
||
- 公司
|
||
- 部门
|
||
- 员工
|
||
- 预算
|
||
|
||
本文档重点解决以下问题:
|
||
|
||
- 如何对外提供统一、可控、可审计的开放接口
|
||
- 如何通过“变更通知 + 主动拉取”的方式降低系统开销
|
||
- 如何通过 7 天拉取窗口、过期处理、补数申请提高安全性
|
||
- 如何在跨数据中心、跨公网传递时通过传输加密与内容加密保障数据安全
|
||
- 如何记录通知、拉取、同步结果,便于事后追踪和排障
|
||
- 如何在现有系统表结构基础上最小代价落地
|
||
|
||
---
|
||
|
||
## 2. 设计结论
|
||
|
||
本方案采用以下核心模式:
|
||
|
||
1. 内部业务数据发生变化后,系统生成变更事件
|
||
2. 系统将变更事件通知给订阅者
|
||
3. 订阅者收到通知后,在 7 天有效窗口内主动拉取最新数据
|
||
4. 超过 7 天后,不再允许直接拉取对应增量数据
|
||
5. 如订阅者错过窗口,必须发起补数申请
|
||
6. 全链路记录通知状态、拉取状态、同步结果和失败原因
|
||
|
||
这是一种兼顾性能、安全和可运维性的企业级设计。
|
||
|
||
---
|
||
|
||
## 3. 总体架构
|
||
|
||
### 3.1 角色
|
||
|
||
- 主系统:当前 `backend` 服务,对外统一提供开放接口
|
||
- 订阅者系统:外部 ERP、HR、财务、BI、协同系统等
|
||
- 开放平台应用:每个外部系统在本系统中注册的独立接入身份
|
||
- 通知中心:负责事件生成、投递、重试、过期、补数审批
|
||
|
||
### 3.2 架构原则
|
||
|
||
- 对外统一入口:全部走 `backend` 的 `/open/v1/*`
|
||
- 内外账号隔离:外部系统不用内部员工登录账号
|
||
- 事件通知最小化:通知消息只传事件元信息,不传完整业务数据
|
||
- 数据主动拉取:业务明细通过开放接口主动拉取
|
||
- 时间窗口受控:增量数据默认仅保留 7 天在线可拉取能力
|
||
- 审计可追踪:每次通知、每次拉取、每次补数都有日志
|
||
- 数据范围受控:必须按 `group_id / company_id / department_id` 控制可见范围
|
||
|
||
### 3.3 推荐流程
|
||
|
||
```text
|
||
组织/员工/预算数据变更
|
||
↓
|
||
写入业务表
|
||
↓
|
||
写入开放事件表 open_event_outbox
|
||
↓
|
||
按订阅关系分发通知
|
||
↓
|
||
订阅者收到消息
|
||
↓
|
||
7天内调用 /open/v1/sync/changes 或对象接口主动拉取
|
||
↓
|
||
记录同步任务、同步明细、游标、审计日志
|
||
↓
|
||
超过7天未拉取 → 事件窗口过期
|
||
↓
|
||
订阅者发起补数申请
|
||
↓
|
||
平台审批后执行事件重放或快照补数
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 时间窗口与过期策略
|
||
|
||
### 4.1 7 天拉取窗口
|
||
|
||
每条变更事件从 `occurred_at` 开始计算,有一个固定的在线增量拉取窗口:
|
||
|
||
- 默认窗口:7 天
|
||
- 字段体现:`available_until = occurred_at + 7天`
|
||
- 在 `available_until` 之前:
|
||
- 允许按 `event_id`
|
||
- 允许按 `cursor`
|
||
- 允许按 `since_time`
|
||
- 允许按资源对象主动拉取最新数据
|
||
|
||
### 4.2 为什么采用 7 天窗口
|
||
|
||
- 足以覆盖大多数下游系统短时故障、周末值守空档、版本发布波动
|
||
- 可以显著降低系统长期保留高频增量拉取资源的压力
|
||
- 可以降低历史数据被无限期遍历和恶意批量拉取的风险
|
||
|
||
### 4.3 过期处理
|
||
|
||
当当前时间大于 `available_until` 时:
|
||
|
||
- 增量接口不再返回该事件对应的明细数据
|
||
- 事件状态从 `available` 进入 `expired`
|
||
- 同步接口返回过期错误
|
||
|
||
建议错误返回:
|
||
|
||
- HTTP:`410 Gone`
|
||
- 业务错误码:`EVENT_WINDOW_EXPIRED`
|
||
|
||
返回示例:
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"code": "EVENT_WINDOW_EXPIRED",
|
||
"message": "该事件的在线拉取窗口已过期,请发起补数申请",
|
||
"data": {
|
||
"event_id": "evt_202607030001",
|
||
"occurred_at": "2026-07-03T09:00:00+08:00",
|
||
"available_until": "2026-07-10T09:00:00+08:00",
|
||
"replay_supported": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 过期后的补救方式
|
||
|
||
过期后不允许直接下载原增量,但允许走受控补数机制:
|
||
|
||
1. 事件重放
|
||
2. 时间段补数
|
||
3. 指定对象补数
|
||
4. 全量快照重建
|
||
|
||
### 4.5 推荐保留策略
|
||
|
||
- 在线可拉取增量明细:7 天
|
||
- 事件元数据与同步日志:至少 180 天
|
||
- 审计日志:至少 180 天,建议 1 年
|
||
- 补数申请记录:长期保留
|
||
|
||
---
|
||
|
||
## 5. 安全设计
|
||
|
||
### 5.1 接入身份
|
||
|
||
每个订阅者系统必须创建独立开放平台应用,不复用内部用户账号。
|
||
|
||
推荐使用:
|
||
|
||
- `client_id`
|
||
- `client_secret`
|
||
- 可选 `app_key`
|
||
- 可选 IP 白名单
|
||
- 可选 mTLS
|
||
|
||
### 5.2 鉴权模式
|
||
|
||
开放平台建议采用两层鉴权:
|
||
|
||
1. `client_id + client_secret` 换取访问令牌
|
||
2. 每次请求携带签名头做防篡改、防重放校验
|
||
|
||
### 5.3 请求头规范
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
- `X-Client-Id`
|
||
- `X-Timestamp`
|
||
- `X-Nonce`
|
||
- `X-Signature`
|
||
- `X-Trace-Id`
|
||
- `X-Content-Encrypted`
|
||
- `X-Encryption-Key-Id`
|
||
- `X-Encryption-Alg`
|
||
|
||
### 5.4 签名规则
|
||
|
||
建议签名原文:
|
||
|
||
```text
|
||
HTTP_METHOD + "\n" +
|
||
PATH + "\n" +
|
||
SORTED_QUERY_STRING + "\n" +
|
||
BODY_SHA256 + "\n" +
|
||
X-Timestamp + "\n" +
|
||
X-Nonce
|
||
```
|
||
|
||
签名算法:
|
||
|
||
- `HMAC-SHA256`
|
||
|
||
### 5.5 安全控制项
|
||
|
||
- 时间戳有效期:5 分钟
|
||
- `nonce` 防重放:同一个 `client_id + nonce` 在有效期内不可重复使用
|
||
- IP 白名单:按开放平台应用控制
|
||
- 限流:按应用、IP、接口三级限流
|
||
- 敏感字段脱敏:手机号、身份证、银行卡等默认脱敏
|
||
- 预算权限分离:预算查询、预算调整、预算审批分开授权
|
||
- 审计全留痕:所有请求、响应、失败原因、耗时必须可追踪
|
||
|
||
### 5.6 跨数据中心安全分层
|
||
|
||
如果接口调用需要跨数据中心并通过互联网传递,安全控制必须分为四层:
|
||
|
||
- 网络层:专线、VPN、云企业网、IPSec 隧道优先;公网直连必须使用固定出口 IP 和白名单。
|
||
- 传输层:强制 HTTPS TLS 1.2+,推荐 TLS 1.3;重要订阅者启用 mTLS 双向证书。
|
||
- 消息层:请求签名、防重放、时间戳、nonce、trace_id 全量校验。
|
||
- 内容层:涉及敏感字段或预算金额时,对响应内容进行字段级或报文级加密。
|
||
|
||
结论:
|
||
|
||
- HTTPS 解决链路加密。
|
||
- HMAC 签名解决身份校验和防篡改。
|
||
- 内容加密解决数据离开系统边界后的二次保护。
|
||
- mTLS 解决服务身份强校验。
|
||
|
||
### 5.7 内容级加密策略
|
||
|
||
内容级加密用于保护业务数据本身,即使 HTTPS 终止在网关、代理、负载均衡或跨中心链路中间节点,也不会暴露明文业务内容。
|
||
|
||
建议按数据敏感度启用:
|
||
|
||
- 普通组织字段:可只使用 HTTPS + 签名。
|
||
- 员工敏感字段:必须字段级加密或脱敏。
|
||
- 身份证、银行卡、薪资、紧急联系人:默认不开放;获批后必须字段级加密。
|
||
- 预算金额、预算计划、预算执行:跨公网传递时建议报文级加密。
|
||
- 补数快照:必须报文级加密,并限制临时窗口有效期。
|
||
|
||
### 5.8 推荐加密方式
|
||
|
||
建议采用信封加密:
|
||
|
||
1. 服务端生成一次性数据密钥 `data_key`
|
||
2. 使用 `AES-256-GCM` 加密业务响应内容
|
||
3. 使用订阅者公钥或平台托管密钥加密 `data_key`
|
||
4. 响应中返回加密后的 `encrypted_key`、`iv`、`tag`、`ciphertext`
|
||
5. 订阅者使用自己的私钥或密钥管理服务解开 `data_key`
|
||
6. 再使用 `data_key` 解密业务内容
|
||
|
||
推荐算法:
|
||
|
||
- 对称加密:`AES-256-GCM`
|
||
- 非对称加密:`RSA-OAEP-256` 或 `SM2`
|
||
- 摘要算法:`SHA-256`
|
||
- 签名算法:`HMAC-SHA256`
|
||
|
||
如果国产化要求较强,可采用:
|
||
|
||
- `SM2` 用于密钥交换或数字信封
|
||
- `SM4-GCM` 用于内容加密
|
||
- `SM3` 用于摘要
|
||
|
||
### 5.9 加密响应格式
|
||
|
||
当 `X-Content-Encrypted: true` 时,响应体建议统一为:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"code": "OK",
|
||
"message": "操作成功",
|
||
"data": {
|
||
"encrypted": true,
|
||
"key_id": "key_20260703_001",
|
||
"alg": "AES-256-GCM",
|
||
"encrypted_key": "base64-rsa-oaep-encrypted-data-key",
|
||
"iv": "base64-iv",
|
||
"tag": "base64-gcm-tag",
|
||
"ciphertext": "base64-encrypted-json"
|
||
}
|
||
}
|
||
```
|
||
|
||
加密前明文示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_202607030001",
|
||
"resource_type": "employee",
|
||
"resource_id": 135,
|
||
"payload": {
|
||
"name": "张三",
|
||
"phone": "13800000000",
|
||
"department_id": 108
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.10 密钥管理要求
|
||
|
||
密钥管理必须独立于业务数据管理。
|
||
|
||
建议要求:
|
||
|
||
- 不在数据库中明文保存任何 `client_secret`、私钥或数据密钥。
|
||
- `client_secret` 只保存哈希。
|
||
- 内容加密主密钥由 KMS、HSM 或独立密钥服务托管。
|
||
- 每个订阅者至少独立配置一个 `key_id`。
|
||
- 支持密钥轮换,旧密钥只保留解密能力,不再用于新数据加密。
|
||
- 支持密钥吊销,吊销后订阅者不能继续拉取加密数据。
|
||
- 审计日志只记录 `key_id` 和加密状态,不记录明文密钥或明文敏感数据。
|
||
|
||
### 5.11 公网传输安全基线
|
||
|
||
跨公网调用必须满足以下基线:
|
||
|
||
- 强制 HTTPS,禁止 HTTP。
|
||
- 启用服务端证书校验,禁止忽略证书错误。
|
||
- 生产环境推荐 mTLS。
|
||
- 配置固定出口 IP 和订阅方 IP 白名单。
|
||
- 所有请求必须签名并校验 `nonce`。
|
||
- 敏感响应必须启用内容加密。
|
||
- 审计日志禁止落明文敏感字段。
|
||
- 补数快照必须走临时窗口和内容加密。
|
||
- 下载类接口必须设置短有效期 URL,且绑定订阅者身份与 IP。
|
||
|
||
---
|
||
|
||
## 6. 数据范围模型
|
||
|
||
### 6.1 基本原则
|
||
|
||
开放平台数据访问必须按租户和组织范围控制,不允许“接口全开放”。
|
||
|
||
### 6.2 推荐范围控制维度
|
||
|
||
- `group_id`
|
||
- `company_id`
|
||
- `department_id`
|
||
- 资源类型
|
||
- 权限范围 `scope`
|
||
|
||
### 6.3 建议权限范围
|
||
|
||
- `group.read`
|
||
- `company.read`
|
||
- `department.read`
|
||
- `employee.read`
|
||
- `budget.read`
|
||
- `sync.read`
|
||
- `sync.replay`
|
||
- `subscription.manage`
|
||
- `audit.read`
|
||
|
||
### 6.4 订阅范围限制
|
||
|
||
每个订阅关系必须明确指定允许访问的范围:
|
||
|
||
- 指定集团
|
||
- 指定公司列表
|
||
- 指定部门列表
|
||
- 指定资源类型
|
||
- 指定事件类型
|
||
|
||
---
|
||
|
||
## 7. 数据同步模型
|
||
|
||
### 7.1 事件类型
|
||
|
||
#### 组织事件
|
||
|
||
- `group.created`
|
||
- `group.updated`
|
||
- `group.deleted`
|
||
- `company.created`
|
||
- `company.updated`
|
||
- `company.deleted`
|
||
- `department.created`
|
||
- `department.updated`
|
||
- `department.deleted`
|
||
- `org.structure.changed`
|
||
|
||
#### 员工事件
|
||
|
||
- `employee.created`
|
||
- `employee.updated`
|
||
- `employee.departed`
|
||
- `employee.reinstated`
|
||
- `employee.transferred`
|
||
|
||
#### 预算事件
|
||
|
||
- `budget.version.created`
|
||
- `budget.version.updated`
|
||
- `budget.subject.updated`
|
||
- `budget.item.updated`
|
||
- `budget.plan.created`
|
||
- `budget.plan.updated`
|
||
- `budget.plan.locked`
|
||
|
||
### 7.2 事件消息最小模型
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_202607030001",
|
||
"trace_id": "trace_20260703_001",
|
||
"event_type": "employee.updated",
|
||
"resource_type": "employee",
|
||
"resource_id": 135,
|
||
"group_id": 209,
|
||
"company_id": 33,
|
||
"department_id": 108,
|
||
"version": 12,
|
||
"occurred_at": "2026-07-03T09:00:00+08:00",
|
||
"available_until": "2026-07-10T09:00:00+08:00",
|
||
"changed_fields": ["department_id", "job_title", "status"],
|
||
"replay_supported": true
|
||
}
|
||
```
|
||
|
||
### 7.3 为什么不在消息里直接放完整数据
|
||
|
||
- 降低通知体积
|
||
- 降低敏感数据在消息通道暴露的风险
|
||
- 便于版本控制
|
||
- 便于重放和幂等处理
|
||
- 便于订阅者自主控制拉取节奏
|
||
|
||
---
|
||
|
||
## 8. 状态机设计
|
||
|
||
### 8.1 事件状态
|
||
|
||
- `pending`:事件已生成,待投递
|
||
- `sent`:已向订阅者下发通知
|
||
- `received`:订阅者已收到通知
|
||
- `acked`:订阅者已确认收到
|
||
- `available`:处于 7 天在线拉取窗口
|
||
- `expired`:已过期,不能再在线拉取
|
||
- `replayed`:已触发重放
|
||
- `closed`:生命周期已关闭
|
||
|
||
### 8.2 同步状态
|
||
|
||
- `pending`:待同步
|
||
- `pulling`:订阅者正在拉取
|
||
- `success`:同步成功
|
||
- `partial_success`:部分成功
|
||
- `failed`:同步失败
|
||
- `retrying`:系统正在重试
|
||
- `stale`:因版本过旧被丢弃
|
||
- `expired`:窗口过期未同步
|
||
|
||
### 8.3 补数申请状态
|
||
|
||
- `pending_review`:待审批
|
||
- `approved`:已批准
|
||
- `rejected`:已拒绝
|
||
- `processing`:补数处理中
|
||
- `completed`:补数完成
|
||
- `failed`:补数失败
|
||
- `cancelled`:已取消
|
||
|
||
---
|
||
|
||
## 9. API 总览
|
||
|
||
开放平台统一前缀:
|
||
|
||
```text
|
||
/open/v1
|
||
```
|
||
|
||
### 9.1 认证接口
|
||
|
||
- `POST /open/v1/auth/token`
|
||
- `POST /open/v1/auth/refresh`
|
||
- `GET /open/v1/auth/me`
|
||
|
||
### 9.2 订阅管理接口
|
||
|
||
- `POST /open/v1/subscriptions`
|
||
- `GET /open/v1/subscriptions`
|
||
- `GET /open/v1/subscriptions/{subscription_id}`
|
||
- `PATCH /open/v1/subscriptions/{subscription_id}`
|
||
- `POST /open/v1/subscriptions/{subscription_id}/enable`
|
||
- `POST /open/v1/subscriptions/{subscription_id}/disable`
|
||
|
||
### 9.3 事件通知接口
|
||
|
||
- `GET /open/v1/events`
|
||
- `GET /open/v1/events/{event_id}`
|
||
- `POST /open/v1/events/{event_id}/ack`
|
||
- `POST /open/v1/events/{event_id}/nack`
|
||
|
||
### 9.4 数据拉取接口
|
||
|
||
- `GET /open/v1/sync/changes`
|
||
- `GET /open/v1/sync/snapshot`
|
||
- `GET /open/v1/groups/{id}`
|
||
- `GET /open/v1/companies/{id}`
|
||
- `GET /open/v1/departments/{id}`
|
||
- `GET /open/v1/employees/{id}`
|
||
- `GET /open/v1/budget-versions/{id}`
|
||
- `GET /open/v1/budget-subjects/{id}`
|
||
- `GET /open/v1/budget-items/{id}`
|
||
- `GET /open/v1/budget-plans/{id}`
|
||
|
||
### 9.5 同步任务接口
|
||
|
||
- `POST /open/v1/sync/jobs`
|
||
- `GET /open/v1/sync/jobs/{job_id}`
|
||
- `POST /open/v1/sync/jobs/{job_id}/complete`
|
||
|
||
### 9.6 补数申请接口
|
||
|
||
- `POST /open/v1/replay-requests`
|
||
- `GET /open/v1/replay-requests`
|
||
- `GET /open/v1/replay-requests/{request_id}`
|
||
- `POST /open/v1/replay-requests/{request_id}/cancel`
|
||
- `POST /open/v1/replay-requests/{request_id}/approve`
|
||
- `POST /open/v1/replay-requests/{request_id}/reject`
|
||
- `POST /open/v1/replay-requests/{request_id}/execute`
|
||
|
||
### 9.7 追踪与审计接口
|
||
|
||
- `GET /open/v1/traces/{trace_id}`
|
||
- `GET /open/v1/audits`
|
||
- `GET /open/v1/subscriptions/{subscription_id}/deliveries`
|
||
- `GET /open/v1/subscriptions/{subscription_id}/sync-records`
|
||
|
||
---
|
||
|
||
## 10. 认证接口设计
|
||
|
||
### 10.1 获取令牌
|
||
|
||
`POST /open/v1/auth/token`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"grant_type": "client_credentials",
|
||
"client_id": "ext_hr_001",
|
||
"client_secret": "******"
|
||
}
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"access_token": "token_xxx",
|
||
"token_type": "Bearer",
|
||
"expires_in": 7200,
|
||
"scope": "company.read department.read employee.read sync.read"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 10.2 刷新令牌
|
||
|
||
`POST /open/v1/auth/refresh`
|
||
|
||
### 10.3 查询当前应用
|
||
|
||
`GET /open/v1/auth/me`
|
||
|
||
返回内容:
|
||
|
||
- 应用 ID
|
||
- 应用名称
|
||
- 权限范围
|
||
- 绑定集团
|
||
- 可见公司
|
||
- 可见部门
|
||
- IP 白名单状态
|
||
|
||
---
|
||
|
||
## 11. 订阅管理接口设计
|
||
|
||
### 11.1 创建订阅
|
||
|
||
`POST /open/v1/subscriptions`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"subscriber_name": "外部HR系统",
|
||
"callback_url": "https://hr.example.com/open/events",
|
||
"pull_base_url": "https://hr.example.com/open/pull",
|
||
"event_types": [
|
||
"department.created",
|
||
"department.updated",
|
||
"employee.created",
|
||
"employee.updated",
|
||
"employee.departed"
|
||
],
|
||
"scope": {
|
||
"group_id": 209,
|
||
"company_ids": [33, 34],
|
||
"department_ids": []
|
||
},
|
||
"security": {
|
||
"signing_method": "hmac-sha256",
|
||
"ip_whitelist": ["10.10.10.10"],
|
||
"allow_replay": true,
|
||
"content_encryption_required": true,
|
||
"encryption_alg": "AES-256-GCM",
|
||
"encryption_key_id": "key_20260703_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"subscription_id": 10001,
|
||
"status": "enabled"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 11.2 查询订阅列表
|
||
|
||
`GET /open/v1/subscriptions`
|
||
|
||
支持筛选:
|
||
|
||
- `status`
|
||
- `subscriber_name`
|
||
- `group_id`
|
||
|
||
### 11.3 启停订阅
|
||
|
||
- `POST /open/v1/subscriptions/{subscription_id}/enable`
|
||
- `POST /open/v1/subscriptions/{subscription_id}/disable`
|
||
|
||
---
|
||
|
||
## 12. 事件通知接口设计
|
||
|
||
### 12.1 拉取事件列表
|
||
|
||
`GET /open/v1/events`
|
||
|
||
查询参数:
|
||
|
||
- `status`
|
||
- `event_type`
|
||
- `since_time`
|
||
- `limit`
|
||
|
||
返回示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"event_id": "evt_202607030001",
|
||
"trace_id": "trace_20260703_001",
|
||
"event_type": "employee.updated",
|
||
"resource_type": "employee",
|
||
"resource_id": 135,
|
||
"occurred_at": "2026-07-03T09:00:00+08:00",
|
||
"available_until": "2026-07-10T09:00:00+08:00",
|
||
"status": "available"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 12.2 事件确认收到
|
||
|
||
`POST /open/v1/events/{event_id}/ack`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"received_at": "2026-07-03T09:00:05+08:00",
|
||
"receiver": "hr-sync-worker-01"
|
||
}
|
||
```
|
||
|
||
### 12.3 事件确认失败
|
||
|
||
`POST /open/v1/events/{event_id}/nack`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"error_code": "SIGNATURE_INVALID",
|
||
"error_message": "签名校验失败"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 数据拉取接口设计
|
||
|
||
### 13.1 增量拉取
|
||
|
||
`GET /open/v1/sync/changes`
|
||
|
||
查询参数:
|
||
|
||
- `since_time`:开始时间
|
||
- `cursor`:上次同步游标
|
||
- `resource_type`:资源类型
|
||
- `event_type`:事件类型
|
||
- `limit`:单次拉取条数,建议不超过 500
|
||
|
||
说明:
|
||
|
||
- 优先使用 `cursor`
|
||
- 没有 `cursor` 时可降级使用 `since_time`
|
||
- 仅返回当前窗口内允许在线拉取的数据
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"cursor": "cur_20260703090000123",
|
||
"has_more": true,
|
||
"items": [
|
||
{
|
||
"event_id": "evt_202607030001",
|
||
"trace_id": "trace_20260703_001",
|
||
"event_type": "employee.updated",
|
||
"resource_type": "employee",
|
||
"resource_id": 135,
|
||
"version": 12,
|
||
"changed_fields": ["department_id", "job_title"],
|
||
"occurred_at": "2026-07-03T09:00:00+08:00",
|
||
"available_until": "2026-07-10T09:00:00+08:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 13.2 全量快照拉取
|
||
|
||
`GET /open/v1/sync/snapshot`
|
||
|
||
查询参数:
|
||
|
||
- `resource_type`
|
||
- `group_id`
|
||
- `company_id`
|
||
- `department_id`
|
||
- `page`
|
||
- `page_size`
|
||
|
||
用途:
|
||
|
||
- 首次接入
|
||
- 下游系统重建
|
||
- 补数审批后全量重拉
|
||
|
||
### 13.3 单对象拉取
|
||
|
||
#### 集团
|
||
|
||
`GET /open/v1/groups/{id}`
|
||
|
||
#### 公司
|
||
|
||
`GET /open/v1/companies/{id}`
|
||
|
||
#### 部门
|
||
|
||
`GET /open/v1/departments/{id}`
|
||
|
||
#### 员工
|
||
|
||
`GET /open/v1/employees/{id}`
|
||
|
||
#### 预算版本
|
||
|
||
`GET /open/v1/budget-versions/{id}`
|
||
|
||
#### 预算科目
|
||
|
||
`GET /open/v1/budget-subjects/{id}`
|
||
|
||
#### 预算项
|
||
|
||
`GET /open/v1/budget-items/{id}`
|
||
|
||
#### 预算计划
|
||
|
||
`GET /open/v1/budget-plans/{id}`
|
||
|
||
### 13.4 单对象接口通用参数
|
||
|
||
- `fields`:指定返回字段
|
||
- `version`:期望版本号
|
||
- `include_deleted`:是否包含已删除记录
|
||
|
||
---
|
||
|
||
## 14. 同步任务接口设计
|
||
|
||
### 14.1 创建同步任务
|
||
|
||
`POST /open/v1/sync/jobs`
|
||
|
||
用途:
|
||
|
||
- 订阅者在开始一轮同步前登记任务
|
||
- 平台记录该次同步和哪些事件关联
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"sync_type": "incremental",
|
||
"subscription_id": 10001,
|
||
"start_cursor": "cur_20260703090000123",
|
||
"resource_types": ["department", "employee"]
|
||
}
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"job_id": "job_20260703_0001",
|
||
"job_status": "pending"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 14.2 查询同步任务
|
||
|
||
`GET /open/v1/sync/jobs/{job_id}`
|
||
|
||
### 14.3 回报同步结果
|
||
|
||
`POST /open/v1/sync/jobs/{job_id}/complete`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"result": "partial_success",
|
||
"pull_started_at": "2026-07-03T09:00:10+08:00",
|
||
"pull_finished_at": "2026-07-03T09:00:22+08:00",
|
||
"synced_count": 120,
|
||
"failed_count": 2,
|
||
"error_items": [
|
||
{
|
||
"resource_type": "employee",
|
||
"resource_id": 138,
|
||
"error_code": "VERSION_CONFLICT",
|
||
"error_message": "下游版本较新,拒绝覆盖"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 15. 7 天过期处理接口行为
|
||
|
||
### 15.1 增量接口过期行为
|
||
|
||
当请求的数据窗口已超过 7 天:
|
||
|
||
- 不返回业务明细
|
||
- 返回过期错误
|
||
- 返回推荐补数路径
|
||
|
||
### 15.2 对象接口过期行为
|
||
|
||
说明:
|
||
|
||
- 单对象接口始终返回对象当前最新状态
|
||
- 但不保证返回“某个过期事件时刻”的历史镜像
|
||
- 如果要恢复当时增量链路,必须走补数申请
|
||
|
||
### 15.3 埋点建议
|
||
|
||
在同步链路中必须记录:
|
||
|
||
- 事件通知时间
|
||
- 订阅者确认收到时间
|
||
- 开始拉取时间
|
||
- 拉取完成时间
|
||
- 同步成功/失败状态
|
||
- 是否因过期进入补数流程
|
||
|
||
---
|
||
|
||
## 16. 补数申请接口设计
|
||
|
||
### 16.1 设计目标
|
||
|
||
补数申请用于处理以下场景:
|
||
|
||
- 下游系统故障错过 7 天窗口
|
||
- 某类资源同步失败需要补拉
|
||
- 指定部门或员工数据需要重新对齐
|
||
- 全量快照需要重建
|
||
|
||
### 16.2 补数模式
|
||
|
||
- `event_replay`:按事件重放
|
||
- `time_range_replay`:按时间段补数
|
||
- `resource_replay`:按对象补数
|
||
- `snapshot_rebuild`:全量快照重建
|
||
|
||
### 16.3 发起补数申请
|
||
|
||
`POST /open/v1/replay-requests`
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"replay_type": "time_range_replay",
|
||
"subscription_id": 10001,
|
||
"reason": "下游系统数据库锁表,7天内未完成同步",
|
||
"resource_types": ["employee", "department"],
|
||
"start_time": "2026-06-20T00:00:00+08:00",
|
||
"end_time": "2026-06-28T23:59:59+08:00",
|
||
"group_id": 209,
|
||
"company_ids": [33]
|
||
}
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"request_id": "rr_202607030001",
|
||
"status": "pending_review"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 16.4 查询补数申请
|
||
|
||
`GET /open/v1/replay-requests`
|
||
|
||
### 16.5 查询补数申请详情
|
||
|
||
`GET /open/v1/replay-requests/{request_id}`
|
||
|
||
### 16.6 取消补数申请
|
||
|
||
`POST /open/v1/replay-requests/{request_id}/cancel`
|
||
|
||
### 16.7 审批补数申请
|
||
|
||
#### 批准
|
||
|
||
`POST /open/v1/replay-requests/{request_id}/approve`
|
||
|
||
#### 驳回
|
||
|
||
`POST /open/v1/replay-requests/{request_id}/reject`
|
||
|
||
### 16.8 执行补数
|
||
|
||
`POST /open/v1/replay-requests/{request_id}/execute`
|
||
|
||
执行方式:
|
||
|
||
- 重新开放临时拉取窗口
|
||
- 生成重放事件批次
|
||
- 生成受控快照任务
|
||
|
||
---
|
||
|
||
## 17. 追踪与审计接口设计
|
||
|
||
### 17.1 查询链路追踪
|
||
|
||
`GET /open/v1/traces/{trace_id}`
|
||
|
||
返回应包含:
|
||
|
||
- 事件信息
|
||
- 投递记录
|
||
- ACK 记录
|
||
- 同步任务
|
||
- 同步结果
|
||
- 失败原因
|
||
- 是否过期
|
||
- 是否补数
|
||
|
||
### 17.2 查询审计日志
|
||
|
||
`GET /open/v1/audits`
|
||
|
||
可按以下条件筛选:
|
||
|
||
- `client_id`
|
||
- `subscription_id`
|
||
- `trace_id`
|
||
- `api_path`
|
||
- `start_time`
|
||
- `end_time`
|
||
- `response_code`
|
||
|
||
---
|
||
|
||
## 18. 错误码设计
|
||
|
||
### 18.1 通用错误码
|
||
|
||
- `INVALID_PARAM`:参数错误
|
||
- `UNAUTHORIZED`:未授权
|
||
- `FORBIDDEN_SCOPE`:超出授权范围
|
||
- `SIGNATURE_INVALID`:签名错误
|
||
- `TIMESTAMP_EXPIRED`:时间戳过期
|
||
- `NONCE_REUSED`:随机串重复使用
|
||
- `RATE_LIMITED`:接口限流
|
||
- `RESOURCE_NOT_FOUND`:资源不存在
|
||
- `VERSION_CONFLICT`:版本冲突
|
||
- `INTERNAL_ERROR`:系统内部错误
|
||
|
||
### 18.2 同步相关错误码
|
||
|
||
- `EVENT_WINDOW_EXPIRED`:事件在线拉取窗口已过期
|
||
- `REPLAY_NOT_ALLOWED`:当前应用无补数权限
|
||
- `REPLAY_REVIEW_REQUIRED`:需要审批后方可补数
|
||
- `SNAPSHOT_TOO_LARGE`:快照范围过大
|
||
- `CURSOR_INVALID`:游标非法
|
||
- `SYNC_JOB_NOT_FOUND`:同步任务不存在
|
||
- `CONTENT_ENCRYPTION_REQUIRED`:当前资源必须启用内容加密
|
||
- `ENCRYPTION_KEY_NOT_FOUND`:未找到可用加密密钥
|
||
- `ENCRYPTION_KEY_REVOKED`:加密密钥已吊销
|
||
- `DECRYPTION_FAILED`:订阅方解密失败或密文格式不正确
|
||
|
||
---
|
||
|
||
## 19. 分页与返回格式
|
||
|
||
### 19.1 统一响应格式
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"code": "OK",
|
||
"message": "操作成功",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
### 19.2 分页响应格式
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [],
|
||
"page": 1,
|
||
"page_size": 50,
|
||
"total": 1200
|
||
}
|
||
}
|
||
```
|
||
|
||
### 19.3 游标响应格式
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [],
|
||
"cursor": "cur_xxx",
|
||
"has_more": true
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 20. 业务对象字段建议
|
||
|
||
### 20.1 集团对象
|
||
|
||
建议字段:
|
||
|
||
- `id`
|
||
- `name`
|
||
- `code`
|
||
- `status`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
### 20.2 公司对象
|
||
|
||
建议字段尽量复用现有 `companies`:
|
||
|
||
- `id`
|
||
- `group_id`
|
||
- `name`
|
||
- `code`
|
||
- `status`
|
||
- `address`
|
||
- `contact_person`
|
||
- `contact_phone`
|
||
- `description`
|
||
- `updated_at`
|
||
|
||
### 20.3 部门对象
|
||
|
||
建议字段尽量复用现有 `departments`:
|
||
|
||
- `id`
|
||
- `group_id`
|
||
- `company_id`
|
||
- `name`
|
||
- `code`
|
||
- `parent_id`
|
||
- `level`
|
||
- `sort_order`
|
||
- `manager_id`
|
||
- `status`
|
||
- `updated_at`
|
||
|
||
### 20.4 员工对象
|
||
|
||
建议字段尽量复用现有 `employees`,但对外输出做裁剪与脱敏:
|
||
|
||
- `id`
|
||
- `group_id`
|
||
- `company_id`
|
||
- `department_id`
|
||
- `user_id`
|
||
- `employee_number`
|
||
- `name`
|
||
- `phone`
|
||
- `email`
|
||
- `status`
|
||
- `job_title`
|
||
- `hire_date`
|
||
- `leave_date`
|
||
- `updated_at`
|
||
|
||
说明:
|
||
|
||
- `id_card`
|
||
- `bank_account`
|
||
- `salary_*`
|
||
- `emergency_*`
|
||
|
||
默认不对外开放,必须经过单独授权。
|
||
|
||
### 20.5 预算对象
|
||
|
||
为减少改造量,建议对外预算同步分 4 类:
|
||
|
||
- `budget_versions`
|
||
- `budget_subjects`
|
||
- `budget_items`
|
||
- `budget_plans`
|
||
|
||
---
|
||
|
||
## 21. 表结构设计
|
||
|
||
本节分为两类:
|
||
|
||
1. 复用现有业务主表
|
||
2. 新增开放平台支撑表
|
||
|
||
### 21.1 复用现有业务主表
|
||
|
||
#### 21.1.1 集团表 `groups`
|
||
|
||
说明:
|
||
|
||
- 当前 `companies.group_id` 已关联 `groups.id`
|
||
- 对外集团数据建议直接基于现有 `groups` 表输出
|
||
|
||
建议对外字段:
|
||
|
||
- `id`
|
||
- `name`
|
||
- `code`
|
||
- `status`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
#### 21.1.2 公司表 `companies`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
- 当前关键字段已满足对外同步主诉求
|
||
|
||
关键字段:
|
||
|
||
- `id`
|
||
- `group_id`
|
||
- `name`
|
||
- `code`
|
||
- `address`
|
||
- `logo_url`
|
||
- `description`
|
||
- `introduction`
|
||
- `legal_representative`
|
||
- `business_license`
|
||
- `status`
|
||
- `company_nature`
|
||
- `contact_person`
|
||
- `contact_phone`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
#### 21.1.3 部门表 `departments`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
|
||
关键字段:
|
||
|
||
- `id`
|
||
- `name`
|
||
- `code`
|
||
- `description`
|
||
- `parent_id`
|
||
- `level`
|
||
- `sort_order`
|
||
- `status`
|
||
- `company_id`
|
||
- `group_id`
|
||
- `manager_id`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
#### 21.1.4 员工表 `employees`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
- 只是在开放接口层做字段裁剪
|
||
|
||
关键字段:
|
||
|
||
- `id`
|
||
- `employee_id`
|
||
- `name`
|
||
- `phone`
|
||
- `email`
|
||
- `hire_date`
|
||
- `department_id`
|
||
- `position_id`
|
||
- `status`
|
||
- `user_id`
|
||
- `company_id`
|
||
- `group_id`
|
||
- `employee_number`
|
||
- `leave_date`
|
||
- `updated_at`
|
||
|
||
#### 21.1.5 用户表 `users`
|
||
|
||
说明:
|
||
|
||
- 认证身份仍然只使用 `users`
|
||
- 员工与账号通过 `employees.user_id -> users.id` 关联
|
||
- 对外不建议直接暴露完整 `users` 表,只在员工对象中按需映射
|
||
|
||
#### 21.1.6 预算版本表 `budget_versions`
|
||
|
||
关键字段:
|
||
|
||
- `id`
|
||
- `version_code`
|
||
- `version_name`
|
||
- `version_type`
|
||
- `budget_year`
|
||
- `start_month`
|
||
- `end_month`
|
||
- `status`
|
||
- `description`
|
||
- `group_id`
|
||
- `company_id`
|
||
- `created_by`
|
||
- `updated_by`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
#### 21.1.7 预算科目表 `budget_subjects`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
- 对外用于同步预算科目树
|
||
|
||
#### 21.1.8 预算项表 `budget_items`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
- 对外用于同步预算项
|
||
|
||
#### 21.1.9 预算计划表 `budget_plans`
|
||
|
||
说明:
|
||
|
||
- 已存在,建议直接复用
|
||
- 对外用于同步预算计划主数据
|
||
|
||
### 21.2 新增开放平台表
|
||
|
||
以下表建议新增,统一使用 `utf8mb4`,并补齐中文注释。
|
||
|
||
#### 21.2.1 开放平台应用表 `open_client_apps`
|
||
|
||
用途:
|
||
|
||
- 管理每个外部接入系统的应用身份
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `client_id` varchar(64) 唯一,应用标识
|
||
- `client_name` varchar(128) 应用名称
|
||
- `client_secret_hash` varchar(255) 密钥哈希
|
||
- `status` varchar(32) 状态:enabled/disabled
|
||
- `group_id` int 允许访问的默认集团
|
||
- `allow_scopes` json 允许的权限范围
|
||
- `ip_whitelist` json 白名单 IP 列表
|
||
- `token_expire_seconds` int 访问令牌有效期秒数
|
||
- `allow_replay` tinyint 是否允许补数申请
|
||
- `content_encryption_required` tinyint 是否强制内容加密
|
||
- `encryption_mode` varchar(32) 加密模式:none/field/payload
|
||
- `encryption_alg` varchar(64) 内容加密算法
|
||
- `active_key_id` varchar(64) 当前启用密钥 ID
|
||
- `mtls_required` tinyint 是否强制 mTLS
|
||
- `public_key_fingerprint` varchar(128) 订阅者公钥指纹
|
||
- `remark` varchar(500) 备注
|
||
- `created_by` int 创建人用户 ID
|
||
- `updated_by` int 更新人用户 ID
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.2 开放平台订阅表 `open_subscriptions`
|
||
|
||
用途:
|
||
|
||
- 管理订阅者接收哪些事件、哪些范围的数据
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `client_app_id` bigint 应用 ID
|
||
- `subscriber_name` varchar(128) 订阅者名称
|
||
- `callback_url` varchar(255) 回调地址
|
||
- `pull_base_url` varchar(255) 拉取基础地址
|
||
- `event_types` json 订阅事件列表
|
||
- `scope_group_id` int 订阅集团范围
|
||
- `scope_company_ids` json 订阅公司范围
|
||
- `scope_department_ids` json 订阅部门范围
|
||
- `resource_types` json 订阅资源类型
|
||
- `status` varchar(32) enabled/disabled
|
||
- `content_encryption_required` tinyint 是否强制内容加密
|
||
- `encryption_key_id` varchar(64) 当前订阅使用的密钥 ID
|
||
- `last_sync_at` datetime 最近同步时间
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.3 开放事件主表 `open_event_outbox`
|
||
|
||
用途:
|
||
|
||
- 记录业务变更产生的标准事件
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `event_id` varchar(64) 唯一事件号
|
||
- `trace_id` varchar(64) 链路追踪号
|
||
- `event_type` varchar(64) 事件类型
|
||
- `resource_type` varchar(32) 资源类型
|
||
- `resource_id` bigint 资源主键
|
||
- `group_id` int 集团 ID
|
||
- `company_id` int 公司 ID
|
||
- `department_id` int 部门 ID
|
||
- `version` bigint 资源版本号
|
||
- `payload` json 事件最小载荷
|
||
- `payload_encrypted` tinyint 事件载荷是否已加密
|
||
- `payload_key_id` varchar(64) 事件载荷加密密钥 ID
|
||
- `payload_alg` varchar(64) 事件载荷加密算法
|
||
- `occurred_at` datetime 事件发生时间
|
||
- `available_until` datetime 在线拉取截止时间
|
||
- `status` varchar(32) pending/sent/available/expired/closed
|
||
- `replay_supported` tinyint 是否允许补数
|
||
- `created_at` datetime 创建时间
|
||
|
||
#### 21.2.4 事件投递表 `open_event_deliveries`
|
||
|
||
用途:
|
||
|
||
- 记录每条事件针对每个订阅者的投递情况
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `event_id` varchar(64) 事件号
|
||
- `subscription_id` bigint 订阅 ID
|
||
- `trace_id` varchar(64) 链路追踪号
|
||
- `delivery_status` varchar(32) sent/received/acked/failed/expired
|
||
- `sent_at` datetime 发送时间
|
||
- `received_at` datetime 接收时间
|
||
- `acked_at` datetime 确认时间
|
||
- `retry_count` int 重试次数
|
||
- `last_retry_at` datetime 最近重试时间
|
||
- `http_status` int 回调响应码
|
||
- `error_code` varchar(64) 错误码
|
||
- `error_message` varchar(500) 错误信息
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.5 同步任务表 `open_sync_jobs`
|
||
|
||
用途:
|
||
|
||
- 记录每一轮增量或全量同步任务
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `job_id` varchar(64) 唯一任务号
|
||
- `subscription_id` bigint 订阅 ID
|
||
- `trace_id` varchar(64) 链路追踪号
|
||
- `sync_type` varchar(32) incremental/full/replay/snapshot
|
||
- `start_cursor` varchar(255) 开始游标
|
||
- `end_cursor` varchar(255) 结束游标
|
||
- `since_time` datetime 增量开始时间
|
||
- `until_time` datetime 增量结束时间
|
||
- `job_status` varchar(32) pending/pulling/success/partial_success/failed
|
||
- `synced_count` int 成功条数
|
||
- `failed_count` int 失败条数
|
||
- `expired_count` int 过期条数
|
||
- `pull_started_at` datetime 开始拉取时间
|
||
- `pull_finished_at` datetime 拉取结束时间
|
||
- `error_message` varchar(500) 错误信息
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.6 同步明细表 `open_sync_job_items`
|
||
|
||
用途:
|
||
|
||
- 记录同步任务内每个对象的执行结果
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `job_id` varchar(64) 任务号
|
||
- `event_id` varchar(64) 事件号
|
||
- `resource_type` varchar(32) 资源类型
|
||
- `resource_id` bigint 资源 ID
|
||
- `version` bigint 资源版本号
|
||
- `item_status` varchar(32) success/failed/skipped/stale/expired
|
||
- `error_code` varchar(64) 错误码
|
||
- `error_message` varchar(500) 错误信息
|
||
- `created_at` datetime 创建时间
|
||
|
||
#### 21.2.7 订阅游标表 `open_sync_cursors`
|
||
|
||
用途:
|
||
|
||
- 保存每个订阅者的最近成功游标
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `subscription_id` bigint 订阅 ID
|
||
- `resource_type` varchar(32) 资源类型
|
||
- `current_cursor` varchar(255) 当前游标
|
||
- `last_event_id` varchar(64) 最近事件号
|
||
- `last_synced_at` datetime 最近同步时间
|
||
- `version` bigint 游标版本
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.8 补数申请表 `open_replay_requests`
|
||
|
||
用途:
|
||
|
||
- 记录订阅者的补数申请和审批结果
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `request_id` varchar(64) 唯一申请号
|
||
- `subscription_id` bigint 订阅 ID
|
||
- `client_app_id` bigint 应用 ID
|
||
- `replay_type` varchar(32) event_replay/time_range_replay/resource_replay/snapshot_rebuild
|
||
- `reason` varchar(500) 申请原因
|
||
- `resource_types` json 资源类型列表
|
||
- `event_ids` json 事件号列表
|
||
- `group_id` int 集团 ID
|
||
- `company_ids` json 公司列表
|
||
- `department_ids` json 部门列表
|
||
- `start_time` datetime 开始时间
|
||
- `end_time` datetime 结束时间
|
||
- `status` varchar(32) pending_review/approved/rejected/processing/completed/failed/cancelled
|
||
- `review_comment` varchar(500) 审批意见
|
||
- `approved_by` int 审批人用户 ID
|
||
- `approved_at` datetime 审批时间
|
||
- `executed_at` datetime 执行时间
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.9 临时补数窗口表 `open_replay_windows`
|
||
|
||
用途:
|
||
|
||
- 为已审批的补数申请生成临时可拉取窗口
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `request_id` varchar(64) 关联申请号
|
||
- `window_token` varchar(128) 补数窗口令牌
|
||
- `start_time` datetime 允许拉取开始时间
|
||
- `end_time` datetime 允许拉取结束时间
|
||
- `expires_at` datetime 临时窗口过期时间
|
||
- `status` varchar(32) active/used/expired/revoked
|
||
- `created_at` datetime 创建时间
|
||
- `updated_at` datetime 更新时间
|
||
|
||
#### 21.2.10 审计日志表 `open_api_audit_logs`
|
||
|
||
用途:
|
||
|
||
- 记录开放接口全部访问痕迹
|
||
|
||
建议字段:
|
||
|
||
- `id` bigint 主键
|
||
- `trace_id` varchar(64) 链路追踪号
|
||
- `client_id` varchar(64) 应用标识
|
||
- `subscription_id` bigint 订阅 ID
|
||
- `api_path` varchar(255) 接口路径
|
||
- `http_method` varchar(16) 请求方法
|
||
- `request_ip` varchar(64) 请求 IP
|
||
- `request_headers` json 请求头摘要
|
||
- `request_body` json 请求体摘要
|
||
- `response_code` int 响应码
|
||
- `response_body` json 响应体摘要
|
||
- `content_encrypted` tinyint 响应内容是否加密
|
||
- `encryption_key_id` varchar(64) 本次响应使用的密钥 ID
|
||
- `cost_ms` int 耗时毫秒
|
||
- `created_at` datetime 创建时间
|
||
|
||
注意:
|
||
|
||
- `request_body` 和 `response_body` 只能保存摘要、字段名、脱敏值或密文摘要。
|
||
- 禁止在审计表中保存身份证、银行卡、薪资、手机号完整明文。
|
||
- 如需排障,应通过 `trace_id`、`event_id`、`job_id` 关联业务链路,不通过审计日志还原明文数据。
|
||
|
||
---
|
||
|
||
## 22. 关键索引建议
|
||
|
||
### 22.1 `open_event_outbox`
|
||
|
||
建议索引:
|
||
|
||
- `uk_event_id (event_id)`
|
||
- `idx_event_type_occurred_at (event_type, occurred_at)`
|
||
- `idx_group_company_occurred_at (group_id, company_id, occurred_at)`
|
||
- `idx_status_available_until (status, available_until)`
|
||
- `idx_resource (resource_type, resource_id, version)`
|
||
|
||
### 22.2 `open_event_deliveries`
|
||
|
||
建议索引:
|
||
|
||
- `idx_subscription_status (subscription_id, delivery_status)`
|
||
- `idx_event_subscription (event_id, subscription_id)`
|
||
- `idx_trace_id (trace_id)`
|
||
|
||
### 22.3 `open_sync_jobs`
|
||
|
||
建议索引:
|
||
|
||
- `uk_job_id (job_id)`
|
||
- `idx_subscription_created_at (subscription_id, created_at)`
|
||
- `idx_job_status (job_status)`
|
||
- `idx_trace_id (trace_id)`
|
||
|
||
### 22.4 `open_replay_requests`
|
||
|
||
建议索引:
|
||
|
||
- `uk_request_id (request_id)`
|
||
- `idx_subscription_status (subscription_id, status)`
|
||
- `idx_created_at (created_at)`
|
||
|
||
---
|
||
|
||
## 23. 版本与幂等要求
|
||
|
||
### 23.1 版本号要求
|
||
|
||
建议所有开放资源同步时都带 `version` 字段。
|
||
|
||
规则:
|
||
|
||
- 同一对象每次有效变更都递增版本号
|
||
- 下游只允许更高版本覆盖更低版本
|
||
- 避免旧消息覆盖新状态
|
||
|
||
### 23.2 幂等要求
|
||
|
||
以下动作必须幂等:
|
||
|
||
- 事件 ACK
|
||
- 同步任务创建
|
||
- 同步结果回报
|
||
- 补数申请创建
|
||
- 补数执行
|
||
|
||
幂等键建议:
|
||
|
||
- `event_id`
|
||
- `job_id`
|
||
- `request_id`
|
||
- `trace_id`
|
||
|
||
---
|
||
|
||
## 24. 落地实施建议
|
||
|
||
### 24.1 第一阶段
|
||
|
||
- 新增开放平台支撑表
|
||
- 接入 `client_id + secret`
|
||
- 先开放组织和员工查询接口
|
||
- 先做通知下发和增量拉取
|
||
|
||
### 24.2 第二阶段
|
||
|
||
- 接入 7 天窗口控制
|
||
- 接入补数申请流程
|
||
- 接入链路追踪和审计报表
|
||
|
||
### 24.3 第三阶段
|
||
|
||
- 接入预算开放接口
|
||
- 接入审批流和预算高敏字段控制
|
||
- 接入控制台页面
|
||
|
||
---
|
||
|
||
## 25. 最终建议
|
||
|
||
本方案最重要的 5 个落地原则如下:
|
||
|
||
1. 对外通知只发事件,不直接发完整业务数据
|
||
2. 增量数据只保留 7 天在线拉取窗口
|
||
3. 过期后必须走受控补数,不允许无限制历史遍历
|
||
4. 同步链路必须全埋点,保证“谁在什么时候同步了什么”可反查
|
||
5. 跨公网传输必须采用 HTTPS + 签名 + 防重放,敏感内容必须做内容级加密
|
||
6. 尽量复用现有 `companies / departments / employees / users / budget_*` 表,降低改造成本
|
||
|
||
---
|
||
|
||
## 26. 后续落地输出建议
|
||
|
||
基于本文档,下一步建议继续输出以下内容:
|
||
|
||
- OpenAPI 3.0 Swagger 草稿
|
||
- MySQL 建表 SQL 脚本
|
||
- 后端模块目录设计
|
||
- 接口权限矩阵
|
||
- 事件发布与补数流程图
|