📋 目录
🌟 一、为什么需要Mock数据?
开发痛点
- 前后端进度不匹配:前端等接口,后端等需求
- 接口频繁变更:影响开发进度
- 测试数据单一:难以覆盖边界情况
- 网络环境依赖:离线无法开发
Mock数据带来的价值
javascript
// 传统开发 vs Mock开发对比
传统开发流程:
需求 → 等接口文档 → 等后端接口 → 联调 → 测试
Mock开发流程:
需求 → 定义接口规范 → Mock数据 → 并行开发 → 无缝切换真实接口
🛠️ 二、Mock.js核心概念
2.1 基本语法
javascript
import Mock from 'mockjs'
// 1. 基础数据类型生成
Mock.mock({
// 随机字符串
'name|1-10': '★', // 生成1-10个星号
'title': '@ctitle(5,10)', // 5-10个汉字
// 随机数字
'age|18-60': 1, // 18-60之间的整数
'score|1-100.2': 1, // 1-100,保留2位小数
// 随机选择
'status|1': ['成功', '失败', '进行中'],
'gender|1': ['男', '女'],
// 占位符
'email': '@email',
'date': '@date("yyyy-MM-dd")',
'image': '@image("200x100", "#4A7BF7", "Mock")',
'color': '@color',
'province': '@province',
'city': '@city(true)', // 带省份
'url': '@url',
'ip': '@ip'
})
// 2. 生成数组
Mock.mock({
'list|5-10': [{ // 生成5-10条数据
'id|+1': 1, // 自增ID
'name': '@cname',
'age|20-40': 1,
'email': '@email',
'address': '@county(true)'
}]
})
2.2 数据模板定义规则
javascript
// 规则:'name|rule': value
const dataTemplate = {
// 1. 属性值是字符串
'string|1-10': '☆', // 重复1-10次
'string|3': 'fixed', // 固定重复3次
// 2. 属性值是数字
'number|+1': 1, // 自增
'number|1-100': 1, // 1-100随机
'number|1-100.2-4': 1, // 1-100随机,小数部分2-4位
// 3. 属性值是布尔值
'boolean|1': true, // 50%概率为true
'boolean|1-9': true, // 1/10概率为true
// 4. 属性值是对象
'object|2': { // 随机选取2个属性
'name': '@cname',
'age': '@integer(20,60)',
'gender': '@pick(["男","女"])',
'email': '@email'
},
// 5. 属性值是数组
'array|1': ['A', 'B', 'C'], // 随机选取1个
'array|1-3': ['A', 'B', 'C'], // 重复1-3次
'array|2': ['A', 'B', 'C'], // 重复2次
}
🚀 三、项目集成方案
3.1 方案一:独立Mock服务(推荐)
javascript
// mock/index.js - Mock服务入口文件
import Mock from 'mockjs'
import user from './modules/user'
import order from './modules/order'
import product from './modules/product'
// 是否启用Mock(可通过环境变量控制)
const isMockEnabled = process.env.VUE_APP_MOCK === 'true'
if (isMockEnabled) {
// 用户相关接口
Mock.mock('/api/user/login', 'post', user.login)
Mock.mock('/api/user/info', 'get', user.getUserInfo)
Mock.mock('/api/user/list', 'get', user.getUserList)
// 订单相关接口
Mock.mock('/api/order/list', 'get', order.getOrderList)
Mock.mock('/api/order/detail', 'get', order.getOrderDetail)
Mock.mock('/api/order/create', 'post', order.createOrder)
// 商品相关接口
Mock.mock('/api/product/list', 'get', product.getProductList)
Mock.mock('/api/product/category', 'get', product.getCategory)
console.log('✅ Mock服务已启动')
}
export default Mock
javascript
// mock/modules/user.js - 用户模块
import Mock from 'mockjs'
export default {
// 登录接口
login: (options) => {
const { username, password } = JSON.parse(options.body)
// 模拟验证逻辑
if (username === 'admin' && password === '123456') {
return Mock.mock({
code: 200,
message: '登录成功',
data: {
token: Mock.mock('@guid'),
userInfo: {
id: Mock.mock('@id'),
username: 'admin',
nickname: Mock.mock('@cname'),
avatar: Mock.mock('@image("100x100", "#4A7BF7", "Avatar")'),
roles: ['admin'],
permissions: ['user:add', 'user:edit', 'user:delete']
}
}
})
} else {
return {
code: 401,
message: '用户名或密码错误',
data: null
}
}
},
// 获取用户信息
getUserInfo: () => {
return Mock.mock({
code: 200,
message: 'success',
data: {
'id|+1': 1,
'username': '@word(3,10)',
'nickname': '@cname',
'avatar': '@image("100x100", "#4A7BF7", "Avatar")',
'email': '@email',
'phone': /^1[3-9]\d{9}$/,
'gender|1': ['male', 'female', 'unknown'],
'age|20-50': 1,
'createTime': '@datetime',
'lastLoginTime': '@datetime',
'status|1': [0, 1], // 0:禁用, 1:启用
'roles|1-3': ['admin', 'editor', 'viewer'],
'department': {
id: '@integer(1, 10)',
name: '@ctitle(3,6)'
}
}
})
},
// 获取用户列表(带分页)
getUserList: (options) => {
const params = new URLSearchParams(options.url.split('?')[1])
const page = parseInt(params.get('page')) || 1
const size = parseInt(params.get('size')) || 10
return Mock.mock({
code: 200,
message: 'success',
data: {
total: 100,
page,
size,
'list|10': [{
'id|+1': (page - 1) * size + 1,
'username': '@word(3,10)',
'nickname': '@cname',
'avatar': '@image("80x80", "#4A7BF7", "头像")',
'email': '@email',
'phone': /^1[3-9]\d{9}$/,
'gender|1': [0, 1, 2], // 0:未知, 1:男, 2:女
'age|20-60': 1,
'status|1': [0, 1],
'createTime': '@datetime("yyyy-MM-dd HH:mm:ss")',
'roles': () => Mock.mock('@pick(["admin","editor","viewer"], 1, 3)'),
'department|1': ['技术部', '市场部', '产品部', '运营部', '行政部']
}]
}
})
}
}
3.2 方案二:Axios拦截器集成
javascript
// utils/mock-interceptor.js
import Mock from 'mockjs'
import { isMockEnabled, mockData } from '../mock'
// 创建自定义适配器
export function createMockAdapter(axiosInstance) {
if (!isMockEnabled) return
axiosInstance.interceptors.request.use(config => {
const { url, method } = config
// 检查是否有对应的Mock数据
const mockHandler = findMockHandler(url, method.toLowerCase())
if (mockHandler) {
config.adapter = function(config) {
return new Promise((resolve, reject) => {
setTimeout(() => { // 模拟网络延迟
try {
const data = mockHandler(config)
const response = {
data,
status: 200,
statusText: 'OK',
headers: {},
config
}
resolve(response)
} catch (error) {
reject(error)
}
}, Mock.Random.integer(200, 800)) // 200-800ms随机延迟
})
}
}
return config
})
}
// 查找Mock处理器
function findMockHandler(url, method) {
const apiKey = `${method.toUpperCase()} ${url}`
// 支持RESTful风格URL
for (const [pattern, handler] of Object.entries(mockData)) {
const [methodPattern, urlPattern] = pattern.split(' ')
if (methodPattern.toLowerCase() === method.toLowerCase()) {
if (url === urlPattern ||
urlPattern.includes(':id') &&
url.startsWith(urlPattern.split(':id')[0])) {
return handler
}
}
}
return null
}
3.3 方案三:Webpack DevServer代理(Vue/React项目)
javascript
// vue.config.js / webpack.config.js
const { createMockMiddleware } = require('mockjs-middleware')
module.exports = {
devServer: {
port: 8080,
before: function(app) {
// 只在开发环境启用Mock
if (process.env.NODE_ENV === 'development') {
// 使用中间件
app.use(createMockMiddleware({
basePath: '/api',
mockDir: path.resolve(__dirname, 'mock'),
watch: true // 监听mock文件变化
}))
}
},
// 或者使用proxy
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': '/mock-api' // 转发到Mock服务
},
bypass: function(req, res, proxyOptions) {
// 根据条件决定是否使用Mock
if (req.headers['x-use-mock']) {
return '/mock' + req.path
}
}
}
}
}
}
🎨 四、高级使用技巧
4.1 动态Mock数据
javascript
// mock/advanced.js
import Mock from 'mockjs'
export default {
// 1. 根据请求参数返回不同数据
searchUsers: (options) => {
const params = new URLSearchParams(options.url.split('?')[1])
const keyword = params.get('keyword')
const page = parseInt(params.get('page')) || 1
// 模拟搜索结果
let users = []
if (keyword) {
users = Mock.mock({
'list|5-15': [{
'id|+1': 1,
'name': keyword + Mock.mock('@cword(1,3)'),
'age|20-40': 1,
'email': keyword.toLowerCase() + Mock.mock('@email').split('@')[1]
}]
}).list
}
return {
code: 200,
data: {
total: users.length,
page,
size: 10,
list: users.slice((page - 1) * 10, page * 10)
}
}
},
// 2. 模拟文件上传
uploadFile: (options) => {
const file = options.body.get('file')
return Mock.mock({
code: 200,
data: {
url: '@url',
name: file?.name || 'file_' + Mock.mock('@now("timestamp")'),
size: Mock.mock('@integer(1024, 102400)'),
type: file?.type || 'image/png',
uploadTime: '@now("yyyy-MM-dd HH:mm:ss")'
},
message: '上传成功'
})
},
// 3. 模拟websocket数据流
mockWebSocketData: () => {
// 生成实时数据流
const generateRealTimeData = () => {
return {
timestamp: new Date().getTime(),
'cpu|20-90.2': 1,
'memory|40-95.2': 1,
'networkIn|100-10000': 1,
'networkOut|100-10000': 1,
'disk|60-99.2': 1,
'connections|100-1000': 1
}
}
return generateRealTimeData()
},
// 4. 模拟分页数据
paginationData: (options) => {
const params = new URLSearchParams(options.url.split('?')[1])
const page = parseInt(params.get('page')) || 1
const pageSize = parseInt(params.get('pageSize')) || 10
const total = 150
// 计算数据范围
const start = (page - 1) * pageSize
const end = Math.min(start + pageSize, total)
const data = Mock.mock({
[`list|${end - start}`]: [{
'id|+1': start + 1,
'name': '@cname',
'status|1': ['pending', 'processing', 'completed', 'failed'],
'progress|0-100': 1,
'createTime': '@datetime',
'updateTime': '@datetime'
}]
})
return {
code: 200,
data: {
list: data.list,
pagination: {
current: page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
}
}
}
}
4.2 TypeScript支持
typescript
// types/mock.d.ts
declare namespace Mock {
interface MockjsRequest {
url: string;
type: string;
body: any;
}
interface MockjsResponse {
[key: string]: any;
}
}
// types/user.ts
export interface User {
id: number;
username: string;
nickname: string;
avatar: string;
email: string;
phone: string;
gender: number;
age: number;
status: number;
createTime: string;
roles: string[];
department: string;
}
export interface LoginResponse {
code: number;
message: string;
data: {
token: string;
userInfo: User;
};
}
// mock/user.ts
import Mock from 'mockjs';
import type { LoginResponse, User } from '../types/user';
const userMock = {
login: (options: Mock.MockjsRequest): LoginResponse => {
const { username, password } = JSON.parse(options.body);
if (username === 'admin' && password === '123456') {
return Mock.mock({
code: 200,
message: '登录成功',
data: {
token: Mock.mock('@guid'),
userInfo: {
id: Mock.mock('@id'),
username: 'admin',
nickname: Mock.mock('@cname'),
avatar: Mock.mock('@image("100x100", "#4A7BF7", "Avatar")'),
email: Mock.mock('@email'),
phone: /^1[3-9]\\d{9}$/,
gender: 1,
age: Mock.mock('@integer(25, 35)'),
status: 1,
createTime: Mock.mock('@datetime'),
roles: ['admin'],
department: '技术部'
} as User
}
});
}
return {
code: 401,
message: '用户名或密码错误',
data: null
};
}
};
export default userMock;
📊 五、最佳实践
5.1 项目结构规范
src/
├── api/ # API接口定义
│ ├── user.ts
│ ├── order.ts
│ └── index.ts
├── mock/ # Mock数据
│ ├── modules/ # 按模块划分
│ │ ├── user.ts
│ │ ├── order.ts
│ │ └── product.ts
│ ├── utils/ # Mock工具函数
│ │ ├── generator.ts
│ │ └── validator.ts
│ ├── types/ # TypeScript类型定义
│ └── index.ts # Mock服务入口
├── services/ # 业务服务层
│ ├── user.service.ts
│ └── order.service.ts
└── utils/ # 工具函数
└── request.ts # 封装axios
5.2 环境配置
javascript
// .env.development
VUE_APP_API_BASE=/api
VUE_APP_MOCK=true # 开发环境启用Mock
VUE_APP_MOCK_DELAY=300 # Mock延迟时间
// .env.production
VUE_APP_API_BASE=https://api.yourdomain.com
VUE_APP_MOCK=false # 生产环境禁用Mock
// config/index.js
const config = {
development: {
baseURL: process.env.VUE_APP_API_BASE,
mock: process.env.VUE_APP_MOCK === 'true',
mockDelay: parseInt(process.env.VUE_APP_MOCK_DELAY) || 300
},
production: {
baseURL: process.env.VUE_APP_API_BASE,
mock: false
}
}
export default config[process.env.NODE_ENV]
5.3 Mock数据规范
javascript
// mock/standards.js
export const MockStandards = {
// 统一响应格式
responseTemplate: {
success: (data, message = 'success') => ({
code: 200,
message,
data,
timestamp: new Date().getTime()
}),
error: (code = 500, message = '服务器错误', data = null) => ({
code,
message,
data,
timestamp: new Date().getTime()
}),
pagination: (list, pagination) => ({
code: 200,
message: 'success',
data: {
list,
pagination
},
timestamp: new Date().getTime()
})
},
// 常用数据模板
dataTemplates: {
user: {
'id|+1': 1,
'username': '@word(3,10)',
'nickname': '@cname',
'avatar': '@image("100x100")',
'email': '@email',
'phone': /^1[3-9]\d{9}$/,
'gender|1': [0, 1, 2],
'age|20-60': 1,
'createTime': '@datetime'
},
product: {
'id|+1': 1,
'name': '@ctitle(5,20)',
'description': '@cparagraph(1,3)',
'price|10-1000.2': 1,
'stock|0-1000': 1,
'status|1': [0, 1],
'images|1-5': ['@image("200x200")'],
'category': '@pick(["电子产品","服装","食品","书籍"])'
}
}
}
5.4 无缝切换真实接口
javascript
// services/request.js
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import mockData from '../mock'
class Request {
constructor() {
this.instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE,
timeout: 10000
})
this.mockAdapter = null
this.initMock()
this.initInterceptors()
}
// 初始化Mock
initMock() {
if (process.env.VUE_APP_MOCK === 'true') {
this.mockAdapter = new MockAdapter(this.instance, {
delayResponse: parseInt(process.env.VUE_APP_MOCK_DELAY) || 300
})
// 注册Mock接口
Object.keys(mockData).forEach(key => {
const [method, url] = key.split(' ')
this.mockAdapter.on(method.toLowerCase(), url).reply(config => {
const handler = mockData[key]
const response = handler(config)
return [200, response]
})
})
console.log('🚀 Mock服务已启动,覆盖接口:', Object.keys(mockData))
}
}
// 动态切换Mock状态
toggleMock(enabled) {
if (this.mockAdapter) {
this.mockAdapter.restore()
}
if (enabled) {
this.initMock()
}
localStorage.setItem('mock-enabled', enabled)
}
// 请求拦截器
initInterceptors() {
// 请求拦截
this.instance.interceptors.request.use(config => {
// 添加Mock标记
if (process.env.VUE_APP_MOCK === 'true') {
config.headers['X-Use-Mock'] = 'true'
}
// 添加token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截
this.instance.interceptors.response.use(
response => {
const { data } = response
// 统一处理业务错误
if (data.code !== 200) {
return Promise.reject(new Error(data.message || '请求失败'))
}
return data
},
error => {
// 网络错误处理
console.error('请求失败:', error)
return Promise.reject(error)
}
)
}
// 通用请求方法
async request(config) {
try {
const response = await this.instance(config)
return response.data
} catch (error) {
throw error
}
}
get(url, params = {}, config = {}) {
return this.request({
method: 'get',
url,
params,
...config
})
}
post(url, data = {}, config = {}) {
return this.request({
method: 'post',
url,
data,
...config
})
}
}
export default new Request()
🚨 六、常见问题解决
6.1 问题:Mock数据太假,不够真实
解决方案: 使用更智能的数据生成器
javascript
// mock/realistic-data.js
import Mock from 'mockjs'
export const RealisticData = {
// 生成更真实的用户数据
generateRealisticUser() {
const firstName = Mock.mock('@cfirst')
const lastName = Mock.mock('@clast')
return {
id: Mock.mock('@id'),
username: Mock.mock('@word(6,12)'),
nickname: firstName + lastName,
firstName,
lastName,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${Mock.mock('@domain')}`,
phone: Mock.mock(/^1[3-9]\d{9}$/),
avatar: `https://randomuser.me/api/portraits/${Mock.mock('@pick(["men","women"])')}/${Mock.mock('@integer(1,99)')}.jpg`,
birthday: Mock.mock('@date("yyyy-MM-dd")'),
address: {
province: Mock.mock('@province'),
city: Mock.mock('@city'),
district: Mock.mock('@county'),
detail: Mock.mock('@cparagraph(1,2)')
},
education: Mock.mock('@pick(["本科","硕士","博士","大专"])'),
jobTitle: Mock.mock('@pick(["工程师","设计师","产品经理","运营","市场"])'),
company: Mock.mock('@cword(4,8)') + '科技有限公司',
salary: Mock.mock('@integer(10000, 50000)'),
interests: Mock.mock('@cword(3,6)').split('').concat(Mock.mock('@cword(3,6)').split('')),
signature: Mock.mock('@csentence')
}
},
// 生成真实的时间序列数据
generateTimeSeriesData(days = 30) {
const data = []
let baseValue = Mock.mock('@integer(100, 500)')
for (let i = 0; i < days; i++) {
const date = new Date()
date.setDate(date.getDate() - i)
// 模拟正常的波动
const change = Mock.mock('@integer(-20, 30)')
baseValue = Math.max(50, baseValue + change)
data.unshift({
date: date.toISOString().split('T')[0],
value: baseValue,
// 模拟工作日/周末差异
weekday: date.getDay(),
isWeekend: date.getDay() === 0 || date.getDay() === 6
})
}
return data
}
}
6.2 问题:如何模拟错误情况?
javascript
// mock/error-scenarios.js
export const ErrorScenarios = {
// 模拟各种HTTP错误
simulateError(scenario = 'network_error') {
const scenarios = {
network_error: {
code: 'NETWORK_ERROR',
message: '网络连接失败,请检查网络设置',
shouldRetry: true
},
timeout: {
code: 'TIMEOUT',
message: '请求超时,请稍后重试',
shouldRetry: true
},
server_error: {
code: 'SERVER_ERROR',
message: '服务器内部错误',
shouldRetry: false
},
unauthorized: {
code: 401,
message: '未授权,请重新登录',
shouldRetry: false,
redirectTo: '/login'
},
forbidden: {
code: 403,
message: '权限不足',
shouldRetry: false
},
not_found: {
code: 404,
message: '资源不存在',
shouldRetry: false
},
validation_error: {
code: 422,
message: '参数验证失败',
errors: {
username: ['用户名不能为空', '用户名长度必须在3-20位之间'],
email: ['邮箱格式不正确']
}
},
rate_limit: {
code: 429,
message: '请求过于频繁,请稍后重试',
retryAfter: 60 // 60秒后重试
}
}
return scenarios[scenario] || scenarios.network_error
},
// 随机返回错误(用于测试)
randomError(probability = 0.1) {
if (Math.random() < probability) {
const errors = [
'network_error',
'timeout',
'server_error',
'unauthorized',
'validation_error'
]
const randomError = errors[Math.floor(Math.random() * errors.length)]
return this.simulateError(randomError)
}
return null
}
}
6.3 问题:Mock影响性能?
优化方案:
javascript
// mock/performance.js
export const PerformanceOptimizer = {
// 1. 懒加载Mock数据
lazyLoadMock(moduleName) {
return import(`./modules/${moduleName}.js`)
.then(module => module.default)
.catch(() => {
console.warn(`Mock模块 ${moduleName} 加载失败`)
return {}
})
},
// 2. 数据缓存
createCachedMock(handler, cacheTime = 60000) {
let cache = null
let cacheTimestamp = 0
return function(...args) {
const now = Date.now()
if (cache && (now - cacheTimestamp) < cacheTime) {
return Promise.resolve(cache)
}
return Promise.resolve(handler(...args))
.then(data => {
cache = data
cacheTimestamp = now
return data
})
}
},
// 3. 批量生成优化
generateBatchData(template, count, batchSize = 1000) {
const result = []
for (let i = 0; i < count; i += batchSize) {
const currentBatchSize = Math.min(batchSize, count - i)
const batch = Mock.mock({
[`data|${currentBatchSize}`]: [template]
}).data
result.push(...batch)
// 避免阻塞主线程
if (i % (batchSize * 10) === 0) {
yield result // 使用generator
}
}
return result
}
}
6.4 问题:如何与后端API保持一致?
javascript
// mock/api-sync.js
export class ApiSynchronizer {
constructor() {
this.apiDefinitions = {}
this.syncInterval = null
}
// 从后端获取API定义
async fetchApiDefinitions() {
try {
const response = await fetch('/swagger.json') // 或后端提供的API文档
const definitions = await response.json()
this.apiDefinitions = this.parseSwagger(definitions)
this.updateMockTemplates()
} catch (error) {
console.warn('无法获取API定义,使用本地Mock配置')
}
},
// 解析Swagger文档
parseSwagger(swaggerJson) {
const apis = {}
Object.entries(swaggerJson.paths || {}).forEach(([path, methods]) => {
Object.entries(methods).forEach(([method, definition]) => {
const key = `${method.toUpperCase()} ${path}`
apis[key] = {
path,
method: method.toUpperCase(),
parameters: definition.parameters || [],
responses: definition.responses || {},
requestBody: definition.requestBody,
tags: definition.tags || [],
summary: definition.summary || ''
}
})
})
return apis
},
// 根据API定义更新Mock模板
updateMockTemplates() {
Object.entries(this.apiDefinitions).forEach(([apiKey, definition]) => {
const { responses } = definition
const successResponse = responses['200'] || responses['201']
if (successResponse && successResponse.schema) {
const mockTemplate = this.generateMockFromSchema(successResponse.schema)
// 更新对应的Mock配置
this.updateMockConfig(apiKey, mockTemplate)
}
})
},
// 从JSON Schema生成Mock模板
generateMockFromSchema(schema) {
// 实现Schema到Mock模板的转换逻辑
// 这里需要根据具体的Schema结构来实现
return {}
},
// 自动同步(定时或监听文件变化)
startAutoSync(interval = 300000) { // 5分钟
this.fetchApiDefinitions()
this.syncInterval = setInterval(() => {
this.fetchApiDefinitions()
}, interval)
},
stopAutoSync() {
if (this.syncInterval) {
clearInterval(this.syncInterval)
}
}
}
📈 七、监控与调试
7.1 Mock数据监控面板
javascript
// mock/monitor.js
export class MockMonitor {
constructor() {
this.requests = []
this.maxRecords = 1000
}
// 记录Mock请求
logRequest(config, response, duration) {
const record = {
id: this.requests.length + 1,
url: config.url,
method: config.method?.toUpperCase(),
params: config.params,
data: config.data,
response: response.data,
status: response.status,
duration,
timestamp: new Date().toISOString(),
fromMock: true
}
this.requests.unshift(record)
// 保持记录数量
if (this.requests.length > this.maxRecords) {
this.requests.pop()
}
// 控制台输出
console.groupCollapsed(`📡 Mock请求: ${record.method} ${record.url}`)
console.log('请求参数:', record.params || record.data)
console.log('响应数据:', record.response)
console.log(`耗时: ${duration}ms`)
console.groupEnd()
return record
}
// 获取统计信息
getStats() {
const total = this.requests.length
const byMethod = {}
const byStatus = {}
let totalDuration = 0
this.requests.forEach(req => {
byMethod[req.method] = (byMethod[req.method] || 0) + 1
byStatus[req.status] = (byStatus[req.status] || 0) + 1
totalDuration += req.duration
})
return {
total,
avgDuration: total ? totalDuration / total : 0,
byMethod,
byStatus,
recentRequests: this.requests.slice(0, 10)
}
}
// 清空记录
clear() {
this.requests = []
}
// 导出数据
exportData(format = 'json') {
const data = {
meta: {
exportedAt: new Date().toISOString(),
totalRecords: this.requests.length
},
requests: this.requests
}
if (format === 'json') {
return JSON.stringify(data, null, 2)
}
// 也可以支持CSV等其他格式
return data
}
}
// 创建全局监控实例
const mockMonitor = new MockMonitor()
// 包装Mock响应函数
export function withMonitoring(handler) {
return function(config) {
const startTime = performance.now()
const response = handler(config)
const endTime = performance.now()
mockMonitor.logRequest(config, {
data: response,
status: 200
}, endTime - startTime)
return response
}
}
🎯 总结
关键要点回顾:
- 明确使用场景:Mock应用于开发、测试阶段,生产环境必须禁用
- 保持数据真实性:使用合理的数据生成规则,避免"假数据"
- 统一规范:制定团队Mock数据规范,确保一致性
- 无缝切换:设计良好的架构,支持Mock/真实接口无感切换
- 持续维护:Mock数据需要随业务需求同步更新
进阶建议:
- 使用OpenAPI/Swagger规范自动生成Mock数据
- 建立Mock数据管理平台,实现团队协作
- 结合单元测试,确保Mock数据的准确性
- 定期清理和维护Mock配置
工具推荐:
- Mock.js:基础数据生成
- MSW (Mock Service Worker):更现代的API Mocking
- Faker.js:更丰富的假数据生成
- JSON Server:快速搭建REST API
- Apifox:API设计、Mock、测试一体化
通过正确使用Mock数据,前端开发可以:
- 🚀 提升开发效率:不依赖后端进度
- 🧪 提高代码质量:提前发现接口问题
- 🔄 促进团队协作:明确接口契约
- 📱 改善用户体验:更真实的数据模拟
记住:Mock不是目的,而是手段,最终目标是为用户提供稳定、高效的产品体验。