工单模型设计:字段定义、状态机、流程模板配置

工单系统能不能玩得转,模型设计是根基。说白了,字段、状态、流程这三样东西没定义好,后面做联动、做自动化全是坑。我这些年见过不少团队,上来就写业务代码,结果后来字段不够用、状态满天飞、流程改不动——全要返工。所以这一章,咱们把模型设计掰开揉碎讲讲。

1. 字段定义——工单的骨架

字段就是工单的数据载体。你想想看,一个工单从创建到关闭,需要记录哪些信息?

  • 核心字段:工单ID、标题、描述、创建人、创建时间、当前状态、优先级。
  • 业务字段:根据场景加,比如故障工单要有“故障分类”、“影响范围”;变更工单要有“变更内容”、“回滚方案”。
  • 扩展字段:用自定义属性或JSON字段兜底,给未来不确定性留口子。

我个人习惯在设计表结构时,把不变的和变的分开。举个例子:

CREATE TABLE ticket (
  id          BIGINT PRIMARY KEY,
  title       VARCHAR(200),
  description TEXT,
  creator     VARCHAR(50),
  created_at  DATETIME,
  status      VARCHAR(30),     -- 当前状态
  priority    TINYINT,
  category    VARCHAR(50),     -- 业务分类
  ext_attr    JSON,            -- 扩展属性兜底
  version     INT DEFAULT 0    -- 乐观锁
);

注意那个 ext_attr 字段。我在项目中遇到过业务方临时要加一个“机房楼层”字段,开发来不及上线新的列,直接往JSON里塞,半小时就搞定了。这就是扩展字段的价值。但也别滥用,核心字段还是要用正式列,否则查询索引和约束没法做。

避坑:字段命名统一风格,别混用 snake_case 和 camelCase。我见过一个工单表里既有 ticket_id 又有 ticketId,后来做数据迁移时解析错位,丢了一周数据。

2. 状态机——工单的生命线

状态机定义了工单从生到死的路径。很多新手喜欢用一堆 if-else 判断流转,结果状态组合成指数级增长,维护成本爆炸。正确的做法是:定义有限状态机(FSM),明确每个状态的可达转换和触发动作。

状态机三个核心要素:

  • 状态(State):工单在某个时刻的稳定阶段,例如待处理、处理中、待验收、已关闭。
  • 事件(Event):触发状态转换的外部动作,例如提交、分配、复核、驳回。
  • 动作(Action):状态转换时附带的业务操作,例如发送通知、记录日志、更新关联资产。

我通常会用一张表把状态机规则存起来,而不是硬编码在代码里。下面是一个典型的状态机表格:

当前状态 事件 目标状态 动作
待处理 受理 处理中 通知处理人,记录受理时间
处理中 完成 待验收 通知发起人,记录完成时间
待验收 通过 已关闭 记录验收人,关闭子任务
待验收 驳回 处理中 退回处理人,附加驳回原因
处理中 转派 待处理 清空当前处理人,重新分配

你想想看,如果这些逻辑写死在代码里,每增加一个状态就要改核心逻辑,风险极高。用配置表驱动状态机,改配置就能上线,这才是运维思维。

个人经验:有一次我接手一个老系统,状态用字符串存了30多种,实际有效的只有8种。重构时把所有状态收敛到一张状态机配置表,代码量砍了60%,Bug率直线下降。
工单典型状态机转换图 待处理 处理中 待验收 已关闭 受理 完成 通过 驳回 转派 状态+事件 => 目标状态+动作 可配置化驱动

3. 流程模板配置——让工单跑起来

有了字段和状态机,还需要流程模板把“谁来做、做什么、按什么顺序做”串起来。流程模板本质上是状态机的超集,它给每个状态分配审批人、限时、抄送等规则。

一个完整的流程模板包括:

  1. 节点定义:每个状态对应一个节点,包含节点名称、处理角色、超时时长。
  2. 转换规则:状态之间的转移条件,支持条件分支(比如金额大于10000需要二级审批)。
  3. 扩展动作:节点进入/离开时执行的hook,例如调用外部API、发送企微消息。

我习惯用YAML来描述流程模板,可读性高,也方便版本管理。给你看一个简化的例子:

# 故障工单流程模板
version: "1.0"
states:
  - name: pending
    label: 待处理
    assigned_role: L1_engineer
    timeout_seconds: 7200
    on_enter:
      - notify_channel: wecom
        template: "新工单 {{ticket_title}} 已分配"
    on_leave:
      - log_action: accepted
  - name: processing
    label: 处理中
    assigned_role: assignee
    allowed_events:
      - event: complete
        target: review
      - event: reassign
        target: pending
        condition: "user.role == 'manager'"
  - name: review
    label: 待验收
    assigned_role: reporter
    timeout_seconds: 3600
技巧:流程模板最好独立于工单实例。模板定义“规则”,实例继承规则但在运行时可以微调(比如换审批人)。我曾在线上系统中把模板和实例严格分离,大促期间紧急调整审批链时,改模板立刻生效,所有实例自动适配。

最后提醒一点:字段、状态机、流程模板三者必须保持数据一致性。字段里的状态字段值,必须对应状态机里定义的状态;流程模板引用的状态,也必须在状态机注册过。我建议用一个统一的状态注册表来管理,生成代码时自动校验,避免出现“运行时报错说状态找不到”的尴尬。

警告:别把流程逻辑和状态逻辑混在一起。曾经有个项目把审批超时的逻辑写在状态转换代码里,后来要改超时时间,找了半天才发现在一个边缘case里hardcode了300秒——完全违背了配置化原则。

好了,模型设计的三个核心要素就聊到这里。字段定义了工单“长什么样”,状态机定义了“怎么变”,流程模板定义了“谁来变”。三者配合好,后面的联动机制才能稳稳落地。