第3章 工单字段设计:元数据模型、必填字段、自定义字段与扩展属性

工单字段设计,说白了就是给告警工单“画骨架”。你想想看,一个工单系统光能通还不够,得知道每个字段该存什么、哪个必填、怎么扩展。我记得刚做告警平台时,团队抄了一个开源的工单表,结果字段不够用,后面又往数据库里加了一堆扩展列,最后搞成了“字段大杂烩”。

这一章,我们就把字段设计拆成三块:元数据模型是地基,必填字段是硬规矩,自定义字段与扩展属性是弹性。我个人习惯先把元数据结构画清楚,再定必填约束,最后留好扩展接口。

3.1 元数据模型:工单的数据骨架

元数据模型就是工单表的核心结构。每个工单在数据库里就是一条记录,字段怎么定义,决定了你能查什么、能关联什么。我一般在设计时把字段分成五组:

分组 字段示例 说明
标识字段 work_order_id, alarm_id, ticket_number 唯一标识一个工单
状态字段 status, severity, phase 记录工单生命周期状态
时间字段 create_time, update_time, deadline 用于SLA计算和时效统计
人员字段 assignee, creator, reviewer 关联用户或角色
内容字段 title, description, resolution 工单主体信息

在实际项目中,我遇到过一种情况:同一张工单要同时关联多个告警。单纯用一个 alarm_id 字段就存不了。这时候就得用关联表,或者把 alarm_ids 设计成 JSON 数组。嗯,后面讲扩展属性时会细说。

元数据模型的核心原则:每个字段职责单一,不贪多,不混用。状态字段不要存文本描述,用枚举;时间字段统一用 UTC 时间戳。

3.2 必填字段:哪些是红线?

必填字段是工单创建的硬门槛。少一个,工单就没法生成。我设计必填字段时,会按“不可绕过的业务规则”来定,而不是按“方便用户”。下面是我总结的五个必填字段,缺一不可。

  1. 告警来源(alarm_source):标识哪个监控系统发来的,没有来源没法溯源。
  2. 告警标题(title):一句话描述问题,不能空着。
  3. 告警等级(severity):P0~P4,决定响应优先级。我曾经在项目中看到有人把等级写成“高”,后来统计时乱成一团,所以必须用枚举。
  4. 创建时间(create_time):系统自动生成,不允许人工填。
  5. 指派人(assignee):工单总得有人接手吧?哪怕暂时指派给待分配队列,也要有个默认值。

小心思:指派人字段在首次创建时可以设成“未分配”,但必须有一个字段记录当前处理人。我给工单加了一个 step 字段,表示当前处理阶段,配合指派字段做流程控制。

3.3 自定义字段:给工单装个“乐高接口”

每家公司的告警场景不一样,有的需要填资源ID,有的需要填影响范围。如果都硬编码在表结构里,每次加字段都要改表,太折腾了。

我推荐的做法是:用键值对(key-value)的方式存自定义字段。主工单表只保留核心字段,额外用一个扩展属性表或 JSON 字段来存个性化内容。

两种常见实现方式:

方案 存储方式 优点 缺点
扩展表 work_order_custom(work_order_id, key, value) 支持索引查询,数据规范 查询时需要join,有一定性能开销
JSON字段 在主表的custom_fields列存JSON 写起来简单,不用建新表 查询复杂,索引受限

我个人偏向先用 JSON 字段做原型,上线后再看是否需要拆成扩展表。为什么?因为可以快速迭代。当年我做一个工单系统,一开始用 JSON 字段存了 20 多个自定义属性,跑了半年,只有 5 个属性被频繁查询。最后才把那 5 个抽成独立字段。

注意:JSON 字段里的自定义属性不要用中文 key,尽量用英文驼峰。否则在些代码做条件过滤时,坑很多。我还见过有人把整段HTML塞进去,结果工单列表页面直接卡死——嗯,这里要限制value长度,建议不超过 1024 字符。

3.4 扩展属性:从“够用”到“好用”

扩展属性跟自定义字段很像,但定位不同。自定义字段是用户主动填的,扩展属性是系统自动携带的上下文信息。比如告警的原始 payload、关联的告警 ID 列表、责任链上下文、自动化执行的 trace 信息。

我通常会把这些扩展属性放进一个单独的 context 字段(JSON 类型),只读不写,供排障和审计用。举个例子:

{
  "original_alarm": {
    "id": "alarm-20250321-001",
    "source": "prometheus",
    "labels": {"severity": "critical", "instance": "10.0.0.1"}
  },
  "related_tickets": ["WO-20250320-045", "WO-20250320-046"],
  "runbook_id": "runbook-ops-007"
}

这样做的好处是:核心工单表仍然轻量,但需要深挖细节时,打开 context 字段就能看到完整上下文。我曾经排一个线上事故,就是因为告警的原始 payload 里含有 timing 信息,而我们工单表里没存,找日志找了半天。从那以后,我坚持把原始告警数据完整写入扩展属性。

3.5 字段设计总图

下面这张 SVG 图把元数据模型、必填字段、自定义字段和扩展属性的关系画出来了。你最好把它收藏起来,做字段设计时拿来对照。

工单字段元数据模型 告警工单 元数据模型 标识字段(id,ticket_number) 状态字段(status,severity) 时间字段(create_time) 人员字段(assignee) 内容字段(title) 必填字段 告警来源(alarm_source) 告警标题(title) 告警等级(severity) 创建时间(create_time) 指派人(assignee) 自定义字段(K-V) 扩展属性(context)

3.6 避坑指南

我来分享几个真实踩过的坑,希望你能绕开:

  • 字段类型选错:我曾经把工单等级字段设计成 tinyint,结果后来需求里加了个“未知”级别,只好改表结构。建议一开始就用枚举或字符串,后面好扩展。
  • 忽略索引:自定义字段用 JSON 存储后,别忘了给常用的 key 建虚拟列索引。否则查一次全表扫描,工单量大了直接慢查询。
  • 给自定义字段设太多选项:有个项目,自定义字段列表允许用户任意添加 key,结果同一个人填了“IP”、“ip”、“ip_address”三个不同 key。最终规范:用下拉选择 + 预定义 key 列表。
  • 扩展属性没做清理:告警原始 payload 可能很大,全量存进去会导致工单表膨胀。建议设一个上限(比如 4KB),超出的截断或存到关联的日志表。

总结一句话:元数据模型稳住核心,必填字段卡住入口,自定义字段和扩展属性给业务留下弹性。按这个思路设计,工单字段未来三年都不用大动。