第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 必填字段:哪些是红线?
必填字段是工单创建的硬门槛。少一个,工单就没法生成。我设计必填字段时,会按“不可绕过的业务规则”来定,而不是按“方便用户”。下面是我总结的五个必填字段,缺一不可。
- 告警来源(alarm_source):标识哪个监控系统发来的,没有来源没法溯源。
- 告警标题(title):一句话描述问题,不能空着。
- 告警等级(severity):P0~P4,决定响应优先级。我曾经在项目中看到有人把等级写成“高”,后来统计时乱成一团,所以必须用枚举。
- 创建时间(create_time):系统自动生成,不允许人工填。
- 指派人(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 图把元数据模型、必填字段、自定义字段和扩展属性的关系画出来了。你最好把它收藏起来,做字段设计时拿来对照。
3.6 避坑指南
我来分享几个真实踩过的坑,希望你能绕开:
- 字段类型选错:我曾经把工单等级字段设计成 tinyint,结果后来需求里加了个“未知”级别,只好改表结构。建议一开始就用枚举或字符串,后面好扩展。
- 忽略索引:自定义字段用 JSON 存储后,别忘了给常用的 key 建虚拟列索引。否则查一次全表扫描,工单量大了直接慢查询。
- 给自定义字段设太多选项:有个项目,自定义字段列表允许用户任意添加 key,结果同一个人填了“IP”、“ip”、“ip_address”三个不同 key。最终规范:用下拉选择 + 预定义 key 列表。
- 扩展属性没做清理:告警原始 payload 可能很大,全量存进去会导致工单表膨胀。建议设一个上限(比如 4KB),超出的截断或存到关联的日志表。
总结一句话:元数据模型稳住核心,必填字段卡住入口,自定义字段和扩展属性给业务留下弹性。按这个思路设计,工单字段未来三年都不用大动。