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 变更记录与传递单的关系模型

传递单与变更记录构成一对多的主从关系。一个传递单可以拥有零条或多条变更记录,每条变更记录必须且只能关联一个传递单。下图展示了这种关系以及变更记录在接口调用链路中的位置。

变更记录与传递单关系模型 传递单 (Transfer) transfer_id (主键) 变更记录 #1 change_id, change_type change_time, operator 变更记录 #2 change_id, change_type change_time, operator 变更记录 #N change_id, change_type change_time, operator 审查意见驱动的接口调用 每次调用生成一条变更记录 变更记录通过 transfer_id 关联传递单,通过 request_id 关联接口调用,形成完整的审计链路

4.5 变更记录生成流程

在审查意见驱动的接口调用中,变更记录的生成遵循以下标准流程:

  1. 接收请求:接口层接收到审查意见提交请求,解析出传递单 ID、审查意见内容、操作人信息。
  2. 校验传递单:校验传递单是否存在、当前状态是否允许执行该变更操作。
  3. 执行变更:更新传递单主表的状态或字段值。
  4. 组装变更内容:根据变更类型,构造 change_content JSON 对象,记录变更前后的值。
  5. 写入变更记录:将完整变更记录插入数据库,确保与传递单更新在同一事务中。
  6. 返回结果:接口返回变更记录 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),以加速审计查询。同时考虑按月或按季度对历史变更记录进行分区归档。