使用 Service Worker 实现请求拦截与 Mock 数据
背景介绍
在前端开发过程中,我们经常需要在后端 API 尚未完成时模拟数据进行开发和测试。传统的 Mock 方案往往需要修改代码或使用特定的 Mock 库,增加了代码复杂度和维护成本。本文介绍的 Service Worker 方案通过拦截网络请求实现无侵入式的 API Mock,使得前端开发可以更加灵活和高效。
实现原理
该方案利用 Service Worker 的网络拦截能力,拦截指定的 API 请求并将其转发到 Mock 服务器。具体流程如下:
---
title: Service Worker 请求 Mock 流程图
---
flowchart TB
A([开始]) --> B[注册 Service Worker\n传入 Mock 配置]
B --> C[Service Worker 监听 fetch 事件]
C --> D{请求匹配\nMock 规则?}
D -->|是| E[构造 Mock 响应头/体]
D -->|否| F[放行原始请求]
E --> G[返回 Mock 响应]
F --> G
G --> H([结束])
style A fill:#4CAF50,color:white
style H fill:#FF5722,color:white
style D stroke:#2196F3,stroke-width:2px
- 注册 Service Worker 并传入 Mock 配置
- Service Worker 监听
fetch事件 - 根据配置判断请求是否需要被 Mock
- 将符合条件的请求转发到 Mock 服务器
- 将 Mock 服务器的响应返回给原始请求
核心文件结构
/src/config/mock/
├── registerMockServiceWorker.js // 注册 Service Worker
├── mockConfig.js // 基础配置文件
└── mockConfig.local.js // 本地环境配置文件
/public/
└── mockServiceWorker.js // Service Worker 实现
配置文件详解
mockConfig.js
这是基础配置文件,定义了默认的 Mock 行为:
// 默认配置
const defaultConfig = {
// 是否启用 mock
enabled: import.meta.env.MODE === 'dev',
// mock 服务器地址
mockServerUrl: '',
// 需要被拦截转发到 mock 服务器的请求前缀
apiPrefixes: [],
// 不需要被 mock 的请求前缀(优先级高于 apiPrefixes)
excludePrefixes: []
};
mockConfig.local.js
本地环境配置文件,可根据个人需求修改:
export default {
enabled: false,
mockServerUrl: 'https://pt-yapi.my4399.com/mock/264',
apiPrefixes: ['/fnflow'],
excludePrefixes: ['/api/v1/no-mock']
};
Service Worker 实现
Service Worker (mockServiceWorker.js) 实现了请求拦截与转发的核心逻辑:
globalThis.addEventListener('fetch', event => {
if (!mockConfig.enabled) {
return;
}
const request = event.request;
const url = new URL(request.url);
// 检查是否需要 mock
const matchedPrefix = mockConfig.apiPrefixes.find(prefix => url.pathname.includes(prefix));
const hasExcludePrefix = mockConfig.excludePrefixes.some(prefix => url.pathname.includes(prefix));
const shouldMock = matchedPrefix && !hasExcludePrefix;
if (!shouldMock) {
return;
}
// 从原始 URL 中提取出以 prefix 开始的部分作为路径
const prefixIndex = url.href.indexOf(matchedPrefix);
const mockPath = url.href.slice(prefixIndex);
// 构建转发到 mock 服务器的 URL
const mockUrl = mockConfig.mockServerUrl + mockPath;
// 创建新的请求并转发
const mockRequest = new Request(mockUrl, {
method: request.method,
headers: request.headers,
body: request.body,
mode: 'cors',
credentials: 'include'
});
event.respondWith(
fetch(mockRequest)
.then(response => response.clone())
.catch(error => {
return new Response(
JSON.stringify({
error: 'Mock server error',
message: error.message
}),
{
status: 500,
headers: {
'Content-Type': 'application/json'
}
}
);
})
);
});
注册 Service Worker
registerMockServiceWorker.js 负责在应用启动时注册 Service Worker:
export async function registerMockServiceWorker() {
if (!mockConfig.enabled) {
return;
}
if ('serviceWorker' in navigator) {
try {
// 将配置转换为 URL 参数
const configParams = new URLSearchParams({
config: JSON.stringify(mockConfig)
}).toString();
const registration = await navigator.serviceWorker.register(`/mockServiceWorker.js?${configParams}`, {
scope: '/'
});
console.log('Mock Service Worker registered:', registration.scope);
} catch (error) {
console.error('Mock Service Worker registration failed:', error);
}
}
}
如何使用
1. 在应用入口处注册 Service Worker
import { registerMockServiceWorker } from './config/mock/registerMockServiceWorker';
// 应用初始化时注册
registerMockServiceWorker();
2. 配置 Mock 服务
- 复制
mockConfig.local.js并根据需要修改:enabled: 是否启用 MockmockServerUrl: Mock 服务器地址(如 YApi、Mock.js 服务等)apiPrefixes: 需要被拦截的 API 前缀excludePrefixes: 不需要被拦截的 API 前缀
3. 开发 Mock 接口
在 Mock 平台(如 YApi)上创建对应的 Mock 接口,确保路径与实际 API 路径一致。
优势与注意事项
优势
- 无侵入性:不需要修改现有代码,通过配置即可启用或禁用
- 灵活性高:可以精确控制哪些请求需要 Mock,哪些走真实接口
- 开发效率:支持热更新,修改 Mock 数据后立即生效
- 团队协作:团队可以共享 Mock 服务,统一数据规范
注意事项
- Service Worker 只能在 HTTPS 或 localhost 环境下运行
- 首次加载页面后 Service Worker 才会生效
- 调试时可通过浏览器的开发者工具查看 Service Worker 状态
- 确保 Mock 服务器返回的数据格式与真实 API 一致
扩展思路
- 条件 Mock:根据特定条件决定是否 Mock,如请求参数、Header 等
- Mock 数据缓存:缓存 Mock 数据以提高性能
- Mock 切换:提供 UI 界面动态切换 Mock 状态
- 请求记录:记录所有请求,用于调试和测试
总结
通过 Service Worker 实现的 API Mock 方案,我们可以在不修改业务代码的情况下灵活地进行前端开发和测试。这种方案特别适合前后端分离的开发模式,能够有效提高开发效率,减少环境依赖。