/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
基于 MSW 与 OpenAPI 的前端契约自动化生成与校验实战

在敏捷迭代与前后端分离的开发体系中,前后端协作的最大的痛点永远是**“接口联调时的口头契约断层”**:
- 后端在 Swagger 文档里写了字段 userId: string,实际返回时变成了 user_id: number;
- 后端接口还在开发中,前端只能硬等或者手动在代码里写各种临时的 mockData 对象;等后端接口开发完毕,前端又要耗费大半天时间删掉 Mock 代码并手动修改接口字段。
依靠人工维护 Mock 数据和接口契约不仅效率低下,而且极易出现“Mock 的时候测得好好的,一联调到处报错”。
通过将 OpenAPI(Swagger 3.0)标准规范、openapi-typescript 静态类型生成、msw 网络拦截 与 zod 运行时契约校验 深度串联,我们构建了一套“后端文档提交即自动生成类型与 Mock、联调时毫秒级捕获契约破坏”的工业化工程闭环。
契约驱动(Contract-First)自动化流水线架构
[后端提交 OpenAPI / Swagger JSON 定义规范]
│
▼ (触发本地脚本: pnpm gen:contract)
┌─────────────────────────────────────────────────────────────┐
│ 自动化代码生成引擎 │
│ ├─ 1. 生成不可变 TypeScript 接口定义 (api.d.ts) │
│ ├─ 2. 自动化生成 MSW Mock Handlers 初始数据模板 │
│ └─ 3. 生成 Zod 运行时响应校验 Schema │
└─────────────────────────────────────────────────────────────┘
│
▼
[前端开发与测试全生命周期消费]
├─ 本地开发: MSW 拦截 Fetch,使用符合契约的高保真 Mock 数据
├─ 单元测试: Vitest 100% 复用 MSW Handlers,无需单独写 Mock
└─ 线上联调: Zod 拦截器一旦发现后端返回缺失字段,立即在控制台高亮预警
核心实现一:自动化生成 TypeScript 类型定义
使用 openapi-typescript 工具,将后端的 openapi.json 直接转译为高精度的 TypeScript 类型定义文件:
# 自动拉取后端最新的 OpenAPI 规范并生成类型
npx openapi-typescript https://api.internal.net/v3/api-docs/swagger.json -o src/types/api-schema.d.ts
生成后的类型完全由后端 Schema 派生,严格消除了手动编写 Interface 的人为手误:
// src/types/api-schema.d.ts (自动生成)
export interface paths {
"/api/v1/orders/{orderId}": {
get: {
parameters: {
path: { orderId: string };
};
responses: {
200: {
content: {
"application/json": {
orderId: string;
amount: number;
status: "PENDING" | "PAID" | "SHIPPED";
createdAt: string;
};
};
};
};
};
};
}
核心实现二:基于 MSW 的类型安全 Mock Handler
利用生成的类型定义,编写 100% 类型安全的 MSW 拦截处理器:
// src/mocks/orderHandlers.ts
import { http, HttpResponse } from 'msw';
import { paths } from '@/types/api-schema';
type OrderResponse = paths['/api/v1/orders/{orderId}']['get']['responses']['200']['content']['application/json'];
export const orderHandlers = [
http.get('/api/v1/orders/:orderId', ({ params }) => {
const { orderId } = params;
// TypeScript 会严格校验返回对象的每个字段是否符合 OpenAPI 规范
const mockOrder: OrderResponse = {
orderId: String(orderId),
amount: 199.99,
status: 'PAID',
createdAt: new Date().toISOString(),
};
return HttpResponse.json(mockOrder);
}),
];
核心实现三:基于 Zod 的运行时契约校验拦截器
在真实联调或灰度测试阶段,通过 Axios / Fetch 响应拦截器,利用 Zod 对后端返回的真实 JSON 进行实时 Schema 校验:
// src/utils/contractGuard.ts
import { z } from 'zod';
// 定义订单响应的运行时校验规则
export const OrderSchema = z.object({
orderId: z.string(),
amount: z.number().positive(),
status: z.enum(['PENDING', 'PAID', 'SHIPPED']),
createdAt: z.string().datetime(),
});
export async function fetchOrderWithGuard(orderId: string) {
const response = await fetch(`/api/v1/orders/${orderId}`);
const rawData = await response.json();
// 核心:运行时契约断言
const parseResult = OrderSchema.safeParse(rawData);
if (!parseResult.success) {
// 后端契约破坏!在控制台输出醒目的红字并自动上报 Sentry
console.error(
`🚨 [契约破坏报警] 接口 /api/v1/orders/${orderId} 返回格式违背 OpenAPI 规范:`,
parseResult.error.format()
);
// 可选择回退到本地安全降级数据或上报告警
}
return rawData;
}




