4. 变更记录模型设计
变更记录是传递单生命周期中所有状态变更、内容修改、审查意见流转的持久化凭证。在审查意见驱动的接口调用场景中,每一次接口请求与响应、每一次状态推进、每一次字段修改,都必须生成一条不可篡改的变更记录。本节将定义变更记录的核心数据结构、字段规范与模型关系。
4.1 变更记录的核心作用
- 审计追溯:记录“谁在什么时间对哪个传递单做了什么变更”,满足合规审计要求。
- 状态还原:通过变更记录序列,可还原传递单任意历史时刻的快照。
- 接口调用凭证:每次审查意见驱动的接口调用,其输入参数与返回结果均以变更记录形式留存。
- 异常排查:当传递单流转异常时,变更记录是定位问题根因的第一手数据。
设计原则:变更记录一旦写入,禁止物理删除或逻辑覆盖。只允许追加新记录,确保审计链完整。
4.2 变更记录数据结构定义
下表列出变更记录模型的核心字段,所有字段均采用统一命名规范,适配企业级关系型数据库与文档型数据库两种存储模式。
| 字段名 | 类型 | 长度/精度 | 必填 | 说明 |
|---|---|---|---|---|
| change_id | VARCHAR / UUID | 36 | 是 | 变更记录唯一标识,全局唯一,建议使用 UUID v4 |
| transfer_id | VARCHAR | 36 | 是 | 关联的传递单 ID,外键指向传递单主表 |
| change_type | VARCHAR | 32 | 是 | 变更类型枚举:STATUS_CHANGE / FIELD_MODIFY / REVIEW_OPINION / SYSTEM_ACTION |
| change_content | JSON / TEXT | — | 是 | 变更内容详情,结构化 JSON 存储变更前后的字段值 |
| change_time | DATETIME | — | 是 | 变更发生时间,精确到毫秒,数据库自动填充 |
| operator | VARCHAR | 64 | 是 | 操作人标识,支持用户 ID 或系统服务账号 |
| source_system | VARCHAR | 64 | 否 | 变更来源系统,用于跨系统集成场景 |
| request_id | VARCHAR | 36 | 否 | 关联的接口调用请求 ID,便于链路追踪 |
4.3 变更类型枚举与内容规范
变更类型决定了 change_content 字段的 JSON 结构。四种核心变更类型及其内容规范如下:
| 变更类型 | 枚举值 | change_content 结构示例 | 典型场景 |
|---|---|---|---|
| 状态变更 | STATUS_CHANGE | {"from": "DRAFT", "to": "SUBMITTED", "reason": "提交审查"} |
传递单从草稿流转到待审查 |
| 字段修改 | FIELD_MODIFY | {"field": "amount", "old": 1000, "new": 1500} |
修改传递单金额字段 |
| 审查意见 | REVIEW_OPINION | {"opinion": "驳回", "comment": "金额超出预算", "reviewer": "张三"} |
审查人填写审查意见 |
| 系统动作 | SYSTEM_ACTION | {"action": "AUTO_APPROVE", "rule": "金额小于1000自动通过"} |
系统自动审批通过 |
设计建议:
change_content 使用 JSON 类型存储,既保留结构化语义,又支持未来扩展新变更类型而不需修改表结构。
4.4 变更记录与传递单的关系模型
传递单与变更记录构成一对多的主从关系。一个传递单可以拥有零条或多条变更记录,每条变更记录必须且只能关联一个传递单。下图展示了这种关系以及变更记录在接口调用链路中的位置。
4.5 变更记录生成流程
在审查意见驱动的接口调用中,变更记录的生成遵循以下标准流程:
- 接收请求:接口层接收到审查意见提交请求,解析出传递单 ID、审查意见内容、操作人信息。
- 校验传递单:校验传递单是否存在、当前状态是否允许执行该变更操作。
- 执行变更:更新传递单主表的状态或字段值。
- 组装变更内容:根据变更类型,构造
change_contentJSON 对象,记录变更前后的值。 - 写入变更记录:将完整变更记录插入数据库,确保与传递单更新在同一事务中。
- 返回结果:接口返回变更记录 ID 与操作结果。
事务一致性要求:传递单状态更新与变更记录写入必须在同一数据库事务中完成。如果写入变更记录失败,传递单的更新必须回滚,反之亦然。这是保证数据一致性的底线。
4.6 变更记录查询接口设计
为支持审计与追溯场景,变更记录模型需要提供以下查询能力:
| 查询场景 | 查询条件 | 排序 | 分页 |
|---|---|---|---|
| 按传递单查询变更历史 | transfer_id | change_time ASC | 是 |
| 按操作人查询操作记录 | operator + 时间范围 | change_time DESC | 是 |
| 按变更类型筛选 | change_type + transfer_id | change_time ASC | 是 |
| 按接口调用追溯 | request_id | — | 否(精确匹配) |
4.7 变更记录模型代码示例
以下为 Python SQLAlchemy 模型定义示例,展示变更记录在 ORM 层面的实现:
from sqlalchemy import Column, String, JSON, DateTime, ForeignKey, Enum
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
import uuid
import enum
class ChangeType(str, enum.Enum):
STATUS_CHANGE = "STATUS_CHANGE"
FIELD_MODIFY = "FIELD_MODIFY"
REVIEW_OPINION = "REVIEW_OPINION"
SYSTEM_ACTION = "SYSTEM_ACTION"
class ChangeRecord(Base):
__tablename__ = "change_records"
change_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
transfer_id = Column(UUID(as_uuid=True), ForeignKey("transfers.transfer_id"), nullable=False)
change_type = Column(Enum(ChangeType), nullable=False)
change_content = Column(JSON, nullable=False)
change_time = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
operator = Column(String(64), nullable=False)
source_system = Column(String(64), nullable=True)
request_id = Column(UUID(as_uuid=True), nullable=True)
def to_dict(self):
return {
"change_id": str(self.change_id),
"transfer_id": str(self.transfer_id),
"change_type": self.change_type.value,
"change_content": self.change_content,
"change_time": self.change_time.isoformat(),
"operator": self.operator,
"source_system": self.source_system,
"request_id": str(self.request_id) if self.request_id else None
}
扩展建议:在生产环境中,建议为
change_records 表建立联合索引 (transfer_id, change_time) 和 (operator, change_time),以加速审计查询。同时考虑按月或按季度对历史变更记录进行分区归档。