新增 POST /api/aihr/org/sync,从开放组织系统 /open/v1/sync/snapshot 拉取公司/部门/员工快照刷新 aihr_org_snapshot;支持 dryRun 预检、 Bearer/client-credentials 鉴权与可选 HMAC 签名;外部未配置时保留 SQL seed。附接口契约设计 v1 与 API/DEV_SETUP 配置说明。
37 KiB
开放平台组织与预算同步 API 设计方案 v1
1. 文档目标
本文档用于定义本系统对外开放的组织与预算同步方案,覆盖以下对象:
- 集团
- 公司
- 部门
- 员工
- 预算
本文档重点解决以下问题:
- 如何对外提供统一、可控、可审计的开放接口
- 如何通过“变更通知 + 主动拉取”的方式降低系统开销
- 如何通过 7 天拉取窗口、过期处理、补数申请提高安全性
- 如何在跨数据中心、跨公网传递时通过传输加密与内容加密保障数据安全
- 如何记录通知、拉取、同步结果,便于事后追踪和排障
- 如何在现有系统表结构基础上最小代价落地
2. 设计结论
本方案采用以下核心模式:
- 内部业务数据发生变化后,系统生成变更事件
- 系统将变更事件通知给订阅者
- 订阅者收到通知后,在 7 天有效窗口内主动拉取最新数据
- 超过 7 天后,不再允许直接拉取对应增量数据
- 如订阅者错过窗口,必须发起补数申请
- 全链路记录通知状态、拉取状态、同步结果和失败原因
这是一种兼顾性能、安全和可运维性的企业级设计。
3. 总体架构
3.1 角色
- 主系统:当前
backend服务,对外统一提供开放接口 - 订阅者系统:外部 ERP、HR、财务、BI、协同系统等
- 开放平台应用:每个外部系统在本系统中注册的独立接入身份
- 通知中心:负责事件生成、投递、重试、过期、补数审批
3.2 架构原则
- 对外统一入口:全部走
backend的/open/v1/* - 内外账号隔离:外部系统不用内部员工登录账号
- 事件通知最小化:通知消息只传事件元信息,不传完整业务数据
- 数据主动拉取:业务明细通过开放接口主动拉取
- 时间窗口受控:增量数据默认仅保留 7 天在线可拉取能力
- 审计可追踪:每次通知、每次拉取、每次补数都有日志
- 数据范围受控:必须按
group_id / company_id / department_id控制可见范围
3.3 推荐流程
组织/员工/预算数据变更
↓
写入业务表
↓
写入开放事件表 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
返回示例:
{
"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 过期后的补救方式
过期后不允许直接下载原增量,但允许走受控补数机制:
- 事件重放
- 时间段补数
- 指定对象补数
- 全量快照重建
4.5 推荐保留策略
- 在线可拉取增量明细:7 天
- 事件元数据与同步日志:至少 180 天
- 审计日志:至少 180 天,建议 1 年
- 补数申请记录:长期保留
5. 安全设计
5.1 接入身份
每个订阅者系统必须创建独立开放平台应用,不复用内部用户账号。
推荐使用:
client_idclient_secret- 可选
app_key - 可选 IP 白名单
- 可选 mTLS
5.2 鉴权模式
开放平台建议采用两层鉴权:
client_id + client_secret换取访问令牌- 每次请求携带签名头做防篡改、防重放校验
5.3 请求头规范
Authorization: Bearer <access_token>X-Client-IdX-TimestampX-NonceX-SignatureX-Trace-IdX-Content-EncryptedX-Encryption-Key-IdX-Encryption-Alg
5.4 签名规则
建议签名原文:
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 推荐加密方式
建议采用信封加密:
- 服务端生成一次性数据密钥
data_key - 使用
AES-256-GCM加密业务响应内容 - 使用订阅者公钥或平台托管密钥加密
data_key - 响应中返回加密后的
encrypted_key、iv、tag、ciphertext - 订阅者使用自己的私钥或密钥管理服务解开
data_key - 再使用
data_key解密业务内容
推荐算法:
- 对称加密:
AES-256-GCM - 非对称加密:
RSA-OAEP-256或SM2 - 摘要算法:
SHA-256 - 签名算法:
HMAC-SHA256
如果国产化要求较强,可采用:
SM2用于密钥交换或数字信封SM4-GCM用于内容加密SM3用于摘要
5.9 加密响应格式
当 X-Content-Encrypted: true 时,响应体建议统一为:
{
"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"
}
}
加密前明文示例:
{
"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_idcompany_iddepartment_id- 资源类型
- 权限范围
scope
6.3 建议权限范围
group.readcompany.readdepartment.reademployee.readbudget.readsync.readsync.replaysubscription.manageaudit.read
6.4 订阅范围限制
每个订阅关系必须明确指定允许访问的范围:
- 指定集团
- 指定公司列表
- 指定部门列表
- 指定资源类型
- 指定事件类型
7. 数据同步模型
7.1 事件类型
组织事件
group.createdgroup.updatedgroup.deletedcompany.createdcompany.updatedcompany.deleteddepartment.createddepartment.updateddepartment.deletedorg.structure.changed
员工事件
employee.createdemployee.updatedemployee.departedemployee.reinstatedemployee.transferred
预算事件
budget.version.createdbudget.version.updatedbudget.subject.updatedbudget.item.updatedbudget.plan.createdbudget.plan.updatedbudget.plan.locked
7.2 事件消息最小模型
{
"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 总览
开放平台统一前缀:
/open/v1
9.1 认证接口
POST /open/v1/auth/tokenPOST /open/v1/auth/refreshGET /open/v1/auth/me
9.2 订阅管理接口
POST /open/v1/subscriptionsGET /open/v1/subscriptionsGET /open/v1/subscriptions/{subscription_id}PATCH /open/v1/subscriptions/{subscription_id}POST /open/v1/subscriptions/{subscription_id}/enablePOST /open/v1/subscriptions/{subscription_id}/disable
9.3 事件通知接口
GET /open/v1/eventsGET /open/v1/events/{event_id}POST /open/v1/events/{event_id}/ackPOST /open/v1/events/{event_id}/nack
9.4 数据拉取接口
GET /open/v1/sync/changesGET /open/v1/sync/snapshotGET /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/jobsGET /open/v1/sync/jobs/{job_id}POST /open/v1/sync/jobs/{job_id}/complete
9.6 补数申请接口
POST /open/v1/replay-requestsGET /open/v1/replay-requestsGET /open/v1/replay-requests/{request_id}POST /open/v1/replay-requests/{request_id}/cancelPOST /open/v1/replay-requests/{request_id}/approvePOST /open/v1/replay-requests/{request_id}/rejectPOST /open/v1/replay-requests/{request_id}/execute
9.7 追踪与审计接口
GET /open/v1/traces/{trace_id}GET /open/v1/auditsGET /open/v1/subscriptions/{subscription_id}/deliveriesGET /open/v1/subscriptions/{subscription_id}/sync-records
10. 认证接口设计
10.1 获取令牌
POST /open/v1/auth/token
请求示例:
{
"grant_type": "client_credentials",
"client_id": "ext_hr_001",
"client_secret": "******"
}
响应示例:
{
"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
请求示例:
{
"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"
}
}
响应示例:
{
"success": true,
"data": {
"subscription_id": 10001,
"status": "enabled"
}
}
11.2 查询订阅列表
GET /open/v1/subscriptions
支持筛选:
statussubscriber_namegroup_id
11.3 启停订阅
POST /open/v1/subscriptions/{subscription_id}/enablePOST /open/v1/subscriptions/{subscription_id}/disable
12. 事件通知接口设计
12.1 拉取事件列表
GET /open/v1/events
查询参数:
statusevent_typesince_timelimit
返回示例:
{
"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
请求示例:
{
"received_at": "2026-07-03T09:00:05+08:00",
"receiver": "hr-sync-worker-01"
}
12.3 事件确认失败
POST /open/v1/events/{event_id}/nack
请求示例:
{
"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 - 仅返回当前窗口内允许在线拉取的数据
响应示例:
{
"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_typegroup_idcompany_iddepartment_idpagepage_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
用途:
- 订阅者在开始一轮同步前登记任务
- 平台记录该次同步和哪些事件关联
请求示例:
{
"sync_type": "incremental",
"subscription_id": 10001,
"start_cursor": "cur_20260703090000123",
"resource_types": ["department", "employee"]
}
响应示例:
{
"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
请求示例:
{
"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
请求示例:
{
"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]
}
响应示例:
{
"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_idsubscription_idtrace_idapi_pathstart_timeend_timeresponse_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 统一响应格式
{
"success": true,
"code": "OK",
"message": "操作成功",
"data": {}
}
19.2 分页响应格式
{
"success": true,
"data": {
"items": [],
"page": 1,
"page_size": 50,
"total": 1200
}
}
19.3 游标响应格式
{
"success": true,
"data": {
"items": [],
"cursor": "cur_xxx",
"has_more": true
}
}
20. 业务对象字段建议
20.1 集团对象
建议字段:
idnamecodestatuscreated_atupdated_at
20.2 公司对象
建议字段尽量复用现有 companies:
idgroup_idnamecodestatusaddresscontact_personcontact_phonedescriptionupdated_at
20.3 部门对象
建议字段尽量复用现有 departments:
idgroup_idcompany_idnamecodeparent_idlevelsort_ordermanager_idstatusupdated_at
20.4 员工对象
建议字段尽量复用现有 employees,但对外输出做裁剪与脱敏:
idgroup_idcompany_iddepartment_iduser_idemployee_numbernamephoneemailstatusjob_titlehire_dateleave_dateupdated_at
说明:
id_cardbank_accountsalary_*emergency_*
默认不对外开放,必须经过单独授权。
20.5 预算对象
为减少改造量,建议对外预算同步分 4 类:
budget_versionsbudget_subjectsbudget_itemsbudget_plans
21. 表结构设计
本节分为两类:
- 复用现有业务主表
- 新增开放平台支撑表
21.1 复用现有业务主表
21.1.1 集团表 groups
说明:
- 当前
companies.group_id已关联groups.id - 对外集团数据建议直接基于现有
groups表输出
建议对外字段:
idnamecodestatuscreated_atupdated_at
21.1.2 公司表 companies
说明:
- 已存在,建议直接复用
- 当前关键字段已满足对外同步主诉求
关键字段:
idgroup_idnamecodeaddresslogo_urldescriptionintroductionlegal_representativebusiness_licensestatuscompany_naturecontact_personcontact_phonecreated_atupdated_at
21.1.3 部门表 departments
说明:
- 已存在,建议直接复用
关键字段:
idnamecodedescriptionparent_idlevelsort_orderstatuscompany_idgroup_idmanager_idcreated_atupdated_at
21.1.4 员工表 employees
说明:
- 已存在,建议直接复用
- 只是在开放接口层做字段裁剪
关键字段:
idemployee_idnamephoneemailhire_datedepartment_idposition_idstatususer_idcompany_idgroup_idemployee_numberleave_dateupdated_at
21.1.5 用户表 users
说明:
- 认证身份仍然只使用
users - 员工与账号通过
employees.user_id -> users.id关联 - 对外不建议直接暴露完整
users表,只在员工对象中按需映射
21.1.6 预算版本表 budget_versions
关键字段:
idversion_codeversion_nameversion_typebudget_yearstart_monthend_monthstatusdescriptiongroup_idcompany_idcreated_byupdated_bycreated_atupdated_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
用途:
- 管理每个外部接入系统的应用身份
建议字段:
idbigint 主键client_idvarchar(64) 唯一,应用标识client_namevarchar(128) 应用名称client_secret_hashvarchar(255) 密钥哈希statusvarchar(32) 状态:enabled/disabledgroup_idint 允许访问的默认集团allow_scopesjson 允许的权限范围ip_whitelistjson 白名单 IP 列表token_expire_secondsint 访问令牌有效期秒数allow_replaytinyint 是否允许补数申请content_encryption_requiredtinyint 是否强制内容加密encryption_modevarchar(32) 加密模式:none/field/payloadencryption_algvarchar(64) 内容加密算法active_key_idvarchar(64) 当前启用密钥 IDmtls_requiredtinyint 是否强制 mTLSpublic_key_fingerprintvarchar(128) 订阅者公钥指纹remarkvarchar(500) 备注created_byint 创建人用户 IDupdated_byint 更新人用户 IDcreated_atdatetime 创建时间updated_atdatetime 更新时间
21.2.2 开放平台订阅表 open_subscriptions
用途:
- 管理订阅者接收哪些事件、哪些范围的数据
建议字段:
idbigint 主键client_app_idbigint 应用 IDsubscriber_namevarchar(128) 订阅者名称callback_urlvarchar(255) 回调地址pull_base_urlvarchar(255) 拉取基础地址event_typesjson 订阅事件列表scope_group_idint 订阅集团范围scope_company_idsjson 订阅公司范围scope_department_idsjson 订阅部门范围resource_typesjson 订阅资源类型statusvarchar(32) enabled/disabledcontent_encryption_requiredtinyint 是否强制内容加密encryption_key_idvarchar(64) 当前订阅使用的密钥 IDlast_sync_atdatetime 最近同步时间created_atdatetime 创建时间updated_atdatetime 更新时间
21.2.3 开放事件主表 open_event_outbox
用途:
- 记录业务变更产生的标准事件
建议字段:
idbigint 主键event_idvarchar(64) 唯一事件号trace_idvarchar(64) 链路追踪号event_typevarchar(64) 事件类型resource_typevarchar(32) 资源类型resource_idbigint 资源主键group_idint 集团 IDcompany_idint 公司 IDdepartment_idint 部门 IDversionbigint 资源版本号payloadjson 事件最小载荷payload_encryptedtinyint 事件载荷是否已加密payload_key_idvarchar(64) 事件载荷加密密钥 IDpayload_algvarchar(64) 事件载荷加密算法occurred_atdatetime 事件发生时间available_untildatetime 在线拉取截止时间statusvarchar(32) pending/sent/available/expired/closedreplay_supportedtinyint 是否允许补数created_atdatetime 创建时间
21.2.4 事件投递表 open_event_deliveries
用途:
- 记录每条事件针对每个订阅者的投递情况
建议字段:
idbigint 主键event_idvarchar(64) 事件号subscription_idbigint 订阅 IDtrace_idvarchar(64) 链路追踪号delivery_statusvarchar(32) sent/received/acked/failed/expiredsent_atdatetime 发送时间received_atdatetime 接收时间acked_atdatetime 确认时间retry_countint 重试次数last_retry_atdatetime 最近重试时间http_statusint 回调响应码error_codevarchar(64) 错误码error_messagevarchar(500) 错误信息created_atdatetime 创建时间updated_atdatetime 更新时间
21.2.5 同步任务表 open_sync_jobs
用途:
- 记录每一轮增量或全量同步任务
建议字段:
idbigint 主键job_idvarchar(64) 唯一任务号subscription_idbigint 订阅 IDtrace_idvarchar(64) 链路追踪号sync_typevarchar(32) incremental/full/replay/snapshotstart_cursorvarchar(255) 开始游标end_cursorvarchar(255) 结束游标since_timedatetime 增量开始时间until_timedatetime 增量结束时间job_statusvarchar(32) pending/pulling/success/partial_success/failedsynced_countint 成功条数failed_countint 失败条数expired_countint 过期条数pull_started_atdatetime 开始拉取时间pull_finished_atdatetime 拉取结束时间error_messagevarchar(500) 错误信息created_atdatetime 创建时间updated_atdatetime 更新时间
21.2.6 同步明细表 open_sync_job_items
用途:
- 记录同步任务内每个对象的执行结果
建议字段:
idbigint 主键job_idvarchar(64) 任务号event_idvarchar(64) 事件号resource_typevarchar(32) 资源类型resource_idbigint 资源 IDversionbigint 资源版本号item_statusvarchar(32) success/failed/skipped/stale/expirederror_codevarchar(64) 错误码error_messagevarchar(500) 错误信息created_atdatetime 创建时间
21.2.7 订阅游标表 open_sync_cursors
用途:
- 保存每个订阅者的最近成功游标
建议字段:
idbigint 主键subscription_idbigint 订阅 IDresource_typevarchar(32) 资源类型current_cursorvarchar(255) 当前游标last_event_idvarchar(64) 最近事件号last_synced_atdatetime 最近同步时间versionbigint 游标版本updated_atdatetime 更新时间
21.2.8 补数申请表 open_replay_requests
用途:
- 记录订阅者的补数申请和审批结果
建议字段:
idbigint 主键request_idvarchar(64) 唯一申请号subscription_idbigint 订阅 IDclient_app_idbigint 应用 IDreplay_typevarchar(32) event_replay/time_range_replay/resource_replay/snapshot_rebuildreasonvarchar(500) 申请原因resource_typesjson 资源类型列表event_idsjson 事件号列表group_idint 集团 IDcompany_idsjson 公司列表department_idsjson 部门列表start_timedatetime 开始时间end_timedatetime 结束时间statusvarchar(32) pending_review/approved/rejected/processing/completed/failed/cancelledreview_commentvarchar(500) 审批意见approved_byint 审批人用户 IDapproved_atdatetime 审批时间executed_atdatetime 执行时间created_atdatetime 创建时间updated_atdatetime 更新时间
21.2.9 临时补数窗口表 open_replay_windows
用途:
- 为已审批的补数申请生成临时可拉取窗口
建议字段:
idbigint 主键request_idvarchar(64) 关联申请号window_tokenvarchar(128) 补数窗口令牌start_timedatetime 允许拉取开始时间end_timedatetime 允许拉取结束时间expires_atdatetime 临时窗口过期时间statusvarchar(32) active/used/expired/revokedcreated_atdatetime 创建时间updated_atdatetime 更新时间
21.2.10 审计日志表 open_api_audit_logs
用途:
- 记录开放接口全部访问痕迹
建议字段:
idbigint 主键trace_idvarchar(64) 链路追踪号client_idvarchar(64) 应用标识subscription_idbigint 订阅 IDapi_pathvarchar(255) 接口路径http_methodvarchar(16) 请求方法request_ipvarchar(64) 请求 IPrequest_headersjson 请求头摘要request_bodyjson 请求体摘要response_codeint 响应码response_bodyjson 响应体摘要content_encryptedtinyint 响应内容是否加密encryption_key_idvarchar(64) 本次响应使用的密钥 IDcost_msint 耗时毫秒created_atdatetime 创建时间
注意:
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_idjob_idrequest_idtrace_id
24. 落地实施建议
24.1 第一阶段
- 新增开放平台支撑表
- 接入
client_id + secret - 先开放组织和员工查询接口
- 先做通知下发和增量拉取
24.2 第二阶段
- 接入 7 天窗口控制
- 接入补数申请流程
- 接入链路追踪和审计报表
24.3 第三阶段
- 接入预算开放接口
- 接入审批流和预算高敏字段控制
- 接入控制台页面
25. 最终建议
本方案最重要的 5 个落地原则如下:
- 对外通知只发事件,不直接发完整业务数据
- 增量数据只保留 7 天在线拉取窗口
- 过期后必须走受控补数,不允许无限制历史遍历
- 同步链路必须全埋点,保证“谁在什么时候同步了什么”可反查
- 跨公网传输必须采用 HTTPS + 签名 + 防重放,敏感内容必须做内容级加密
- 尽量复用现有
companies / departments / employees / users / budget_*表,降低改造成本
26. 后续落地输出建议
基于本文档,下一步建议继续输出以下内容:
- OpenAPI 3.0 Swagger 草稿
- MySQL 建表 SQL 脚本
- 后端模块目录设计
- 接口权限矩阵
- 事件发布与补数流程图