4. 数据格式规范:JSON Schema 定义、字段约束、版本控制策略

做接口对接,最怕什么?我最怕两边对字段解释不一样。你发个 status 字段,他以为 0 是成功,你以为是 1。这种问题,在 DLP 外发审计场景里特别要命——审计日志一旦格式乱了,整个闭环就堵死了。

所以这一章,我们专门聊聊数据格式规范。说白了,就是用一套严格的标准把接口的“长相”定死。我个人经验是:前期格式规范做得越细,联调时吵的架就越少。

4.1 为什么需要数据格式规范?

先看一个真实场景。DLP 系统外发告警时,会把事件序列化成 JSON 推给第三方。有一次,我们的开发同事忘了加 severity 字段,第三方直接报错。为什么?因为他们那边的 JSON Schema 校验没通过,整条消息被丢了。

你看,没规范就像没红绿灯。每个人按自己理解开车,不出事故才怪。

核心结论: 数据格式规范 = 接口的“红绿灯” + “交通指示牌”。没有它,系统间通信就是盲人摸象。

4.2 JSON Schema 定义

JSON Schema 是什么?它就是一个 JSON 文件,用来描述另一个 JSON 长什么样。有点像我们填表格时的“填表说明”。

我习惯先定义 Schema,再写代码。这样前端后端都看着同一个标准,谁也跑不了。

4.2.1 基础结构

一个简单的例子:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/audit-event.schema.json",
  "title": "审计事件",
  "type": "object",
  "properties": {
    "eventId": {
      "type": "string",
      "description": "全局唯一事件 ID"
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "description": "事件发生时间(ISO 8601)"
    },
    "severity": {
      "type": "integer",
      "minimum": 0,
      "maximum": 5,
      "description": "告警严重等级"
    }
  },
  "required": ["eventId", "timestamp", "severity"]
}

注意看 $id 字段。我建议把它设成一个可访问的 URL,方便团队直接打开浏览器查看。万一有人改 Schema,也能追溯。

小提示: 我一般把 Schema 文件放在项目的 schemas/ 目录下,版本控制走 Git。这样每次修改都有记录,出问题能回退。

4.2.2 引用与复用

当字段太多时,可以拆成子 Schema,用 $ref 引用。比如把“用户信息”单独定义:

{
  "definitions": {
    "user": {
      "type": "object",
      "properties": {
        "userId": { "type": "string" },
        "userName": { "type": "string" }
      },
      "required": ["userId"]
    }
  },
  "properties": {
    "sender": { "$ref": "#/definitions/user" },
    "receiver": { "$ref": "#/definitions/user" }
  }
}

这样做的好处是,修改只需一处,所有引用自动生效。我经历过一个项目,因为没复用,改了五六个副本,漏了一个,测试才暴露——惨痛教训。

4.3 字段约束

光定义类型还不够。你想想看,"severity": 6 这种值应该被拒绝,因为它超出了 0~5 的范围。字段约束,就是给数据加一把锁。

4.3.1 常用约束类型

约束类型关键词示例说明
类型type"type": "integer"string / number / integer / boolean / array / object / null
最小/最大值minimum, maximum"minimum": 0, "maximum": 5适用于 number / integer
字符串长度minLength, maxLength"minLength": 1, "maxLength": 128防止恶意超长字符串
正则模式pattern"pattern": "^[a-zA-Z0-9_]+$"限制字符集,比如用户ID
枚举enum"enum": ["low", "medium", "high"]只能取给定值之一
格式format"format": "email"date-time / email / uri / ipv4 等
数组长度minItems, maxItems"minItems": 1, "maxItems": 100限制数组大小
额外属性additionalProperties"additionalProperties": false禁止出现未定义字段

这里我重点说一下 additionalProperties。我曾经在对接第三方时,他们悄悄加了一个 secret 字段,结果我们解析时没处理,程序直接崩溃。从那以后,我所有公开接口的 Schema 都加上 "additionalProperties": false——谁也别想偷偷塞东西。

4.3.2 业务约束示例

审计事件里有个 action 字段,表示操作类型。我们规定只能取 create、read、update、delete 四个值。用枚举约束:

"action": {
  "type": "string",
  "enum": ["create", "read", "update", "delete"]
}

再比如 fileSize,单位是字节,不能为负数:

"fileSize": {
  "type": "integer",
  "minimum": 0,
  "maximum": 1073741824
}

为什么上限是 1GB?因为我们的 DLP 系统对超过 1GB 的文件会单独处理,不走标准外发通道。这个约束是跟业务挂钩的,不是拍脑袋定的。

4.4 版本控制策略

接口会变,Schema 也会变。问题来了:改了 Schema 后,旧版调用方怎么办?

我见过最粗暴的做法:直接通知对方“我们接口更新了,你们快点改”。结果对方说“我们排期到三个月后了”。两边都不爽。

所以版本控制,解决的核心是“兼容”与“平滑演进”。

4.4.1 版本号怎么放?

两种主流方式:

  • URL 路径版本:/api/v1/audit、/api/v2/audit。简单直观,但 URL 会变长。
  • 请求头版本:在 Accept 头里指定版本,比如 Accept: application/vnd.company.audit+json; version=2。更 RESTful,但客户端实现稍复杂。

我个人偏向 URL 路径版本。为什么?因为浏览器里直接敲 URL 就能测试,对调试友好。但如果你做的是纯内部微服务,可以用请求头版本,省得 URL 结构复杂。

4.4.2 兼容性规则

改 Schema 时,要遵循“向前兼容”原则。简单说:老客户端用老 Schema 发送的请求,新服务必须能正确处理。

改动类型是否兼容举例
增加可选字段✅ 兼容新增 description 字段,不是 required
增加必填字段❌ 不兼容老数据没有这个字段,会校验失败
缩小枚举值范围❌ 不兼容原来 enum: [a,b,c] 变成 [a,b],老客户端发的 c 被拒绝
扩大枚举值范围✅ 兼容新增一个值,老客户端没发它,没问题
放宽约束(降低最小值)✅ 兼容minimum 从 0 改成 -10,老数据 0 仍合法
收紧约束(提高最小值)❌ 不兼容老数据可能低于新最小值
删除字段❌ 不兼容老客户端还在发这个字段,新服务可能不认识而忽略,但建议保留
注意: 即使你只在内部使用,也别想“偷偷改 Schema 不通知”。我见过有同事把必填字段从 userId 改成 userAccount,结果下游系统三天后才发现。版本升级必须走正式变更流程。

4.4.3 多版本共存策略

如果实在要做不兼容变更,建议同时发布两个版本,给调用方一段迁移时间。比如:

  • /api/v1/audit 保持老 Schema
  • /api/v2/audit 使用新 Schema

然后在 Router 层加一个版本分发。我习惯用 Nginx 或网关做:根据请求路径前缀转发到不同的微服务实例。这样旧版服务可以单独维护,直到所有调用方都迁移完再下线。

迁移窗口: 我一般给 3 个月的公告期,加上 1 个月的并行期。并行期内,旧版仍运行但标记为“即将弃用”,返回 Warning: deprecation 响应头提醒调用方。

4.5 知识结构图

下面我把这节的知识点串成一张图。你可以用它当大纲,写 Schema 时对照着检查。

数据格式规范知识体系 数据格式规范 JSON Schema 定义 字段约束 版本控制策略 基础结构 \$ref 引用 类型/长度 枚举/格式 业务规则 版本号位置 兼容性规则 多版本共存 核心:先定 Schema → 再加约束 → 最后管理版本演进 三者缺一不可,才能保证外发审计闭环不出错

4.6 落地建议

最后,我把这章的核心动作总结成三步:

  1. 编写 JSON Schema:用 $schema 指定草案版本,用 $id 标识唯一地址。所有字段都配上 description,给后来人看。
  2. 施加字段约束:能加 minimum 就加,能加 enum 就加,additionalProperties 默认设成 false。别怕约束多,怕的是约束不够。
  3. 规划版本策略:选一种版本号位置,定好兼容性规则。不兼容改动时必须给迁移窗口,别搞“突然死亡”。
我的个人习惯: 每次上线前,我会跑一遍自动化测试——用 Mock 数据对 Schema 做正反两面校验。正向:构造合法数据,确认校验通过。反向:故意改坏值,确认校验拒绝。这样能提前发现隐藏问题。