3、接口文档与契约:Swagger/OpenAPI规范、接口文档自动生成、前后端契约测试

说实话,我在大屏项目里踩过最大的坑,就是接口文档。

有一次,后端同学改了接口返回字段,没通知前端。结果大屏上线那天,所有图表都显示「数据加载失败」。老板站在大屏前,脸都绿了。嗯,从那以后,我立了个规矩:没有文档的接口,不允许联调

3.1 为什么需要接口文档?

你想想看,大屏项目通常涉及多个数据源。有的来自实时流,有的来自离线数仓,还有的来自第三方API。如果没有一份清晰的文档,前后端就像在黑暗中跳舞——踩脚是迟早的事。

我个人习惯,把接口文档当作「契约」。它不只是写给前端看的,更是写给未来接手的人看的。说白了,文档就是代码的一部分。

核心原则:接口文档应该先于代码编写。先定义契约,再各自实现。

3.2 Swagger/OpenAPI 规范

现在业界最通用的标准就是 OpenAPI 规范(以前叫 Swagger)。它用 JSON 或 YAML 描述整个 API,包括路径、参数、返回值、认证方式等。

我推荐用 YAML 格式,可读性更好。来看一个实际例子:

openapi: 3.0.0
info:
  title: 大屏数据接口
  version: 1.0.0
  description: 实时大屏数据查询接口

paths:
  /api/v1/realtime/sales:
    get:
      summary: 获取实时销售额
      parameters:
        - name: date
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: region
          in: query
          required: false
          schema:
            type: string
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  data:
                    type: object
                    properties:
                      totalSales:
                        type: number
                      growthRate:
                        type: number
                  message:
                    type: string

这段描述,前端一看就知道要传什么参数、返回什么结构。我在项目中遇到过,有些团队喜欢用 JSON 格式,但 YAML 更简洁,尤其当接口数量多的时候,缩进结构一目了然。

3.3 接口文档自动生成

手写文档?别傻了。我见过最惨的案例:文档写了 200 页,代码改了 300 次,文档一次没更新。最后前端拿着过期的文档联调,三天没调通。

正确的做法是:从代码自动生成文档

以 Java 的 Spring Boot 为例,用 springdoc-openapi 库,加几个注解就行:

@RestController
@RequestMapping("/api/v1/realtime")
@Tag(name = "实时数据接口", description = "大屏实时数据查询")
public class RealtimeController {

    @GetMapping("/sales")
    @Operation(summary = "获取实时销售额")
    public ApiResponse<SalesVO> getSales(
            @Parameter(description = "日期", required = true)
            @RequestParam String date,
            @Parameter(description = "区域")
            @RequestParam(required = false) String region) {
        // 业务逻辑
    }
}

启动项目后,访问 /swagger-ui.html 就能看到完整的文档页面。前端可以直接在页面上测试接口,连 Postman 都省了。

小技巧:我习惯在 CI/CD 流程里加一步「文档校验」。如果接口定义和代码不一致,构建直接失败。这样能保证文档永远是最新的。

3.4 前后端契约测试

文档有了,但怎么保证双方都遵守契约?

我曾经遇到一个坑:后端说「我返回的字段名是 camelCase」,前端说「我收到的是 snake_case」。查了半天,发现是序列化配置不一致。这种问题,靠人眼检查是看不出来的。

解决方案是契约测试。我推荐用 Pact 框架,它可以在单元测试阶段验证接口是否满足契约。

前端侧(以 JavaScript 为例):

const { Pact } = require('@pact-foundation/pact');
const provider = new Pact({
  consumer: 'Frontend',
  provider: 'Backend',
  port: 1234,
});

describe('Sales API', () => {
  beforeAll(() => provider.setup());

  afterAll(() => provider.finalize());

  test('should return sales data', async () => {
    await provider.addInteraction({
      state: 'has sales data',
      uponReceiving: 'a request for sales',
      withRequest: {
        method: 'GET',
        path: '/api/v1/realtime/sales',
        query: { date: '2024-01-01' },
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          code: 0,
          data: {
            totalSales: 1000000,
            growthRate: 0.15,
          },
        },
      },
    });

    const response = await fetch('http://localhost:1234/api/v1/realtime/sales?date=2024-01-01');
    const body = await response.json();
    expect(body.code).toBe(0);
    expect(body.data.totalSales).toBeDefined();
  });
});

后端侧(以 Java 为例):

@PactVerification(fragment = "pactFile")
@Test
void verifySalesContract() {
    // Pact 会自动启动 Mock Server,验证接口是否满足契约
}

这样做的好处是:任何一方修改了接口,测试就会失败。双方必须协商一致才能通过。说白了,契约测试就是给接口上了一把「锁」。

注意:契约测试不能替代集成测试。它只验证接口的「形状」是否正确,不验证业务逻辑。我一般会在 CI 里同时跑契约测试和集成测试,双重保险。

3.5 大屏场景的特殊考虑

大屏接口和普通 Web 接口不太一样。我总结了几点经验:

  • 数据格式要统一:所有接口返回统一格式,比如 { code, data, message }。前端不用为每个接口写不同的解析逻辑。
  • 时间字段用时间戳:大屏经常需要做时间对比,用字符串格式容易出 bug。我习惯用毫秒级时间戳,前端直接 new Date() 就行。
  • 空值处理要明确:大屏数据可能为空,接口文档里要明确说明空值时的行为。是返回 null 还是空对象?前端需要提前处理。
  • 分页参数要规范:大屏通常不分页,但如果有历史数据查询,分页参数要统一。我推荐用 pagesize,而不是 offsetlimit
场景 推荐做法 不推荐做法
时间字段 毫秒时间戳 yyyy-MM-dd HH:mm:ss 字符串
空值处理 明确返回 null 或空对象 不返回该字段
分页参数 page + size offset + limit
错误码 统一错误码表 每个接口自定义错误码

嗯,最后说一句:文档和契约不是束缚,而是解放。有了它们,前后端可以并行开发,互不干扰。我在团队里推行这套规范后,联调时间从原来的 3 天缩短到了半天。这才是真正的效率提升。