whistle.mock-plugins
Version:
Whistle 插件,用于快速创建 API 模拟数据
345 lines (301 loc) • 11.3 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const Mock = require('mockjs');
const url = require('url');
// 生成随机数工具函数(与rules-server.js中保持一致)
const generateRandomValue = (pattern) => {
// 如果不是以@开头的模式,直接返回原值
if (!pattern || !pattern.startsWith('@')) {
return pattern;
}
const formatPattern = pattern.substring(1); // 去掉@前缀
// 为每个x生成一个随机字符(字母或数字)
return formatPattern.replace(/x/g, () => {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return chars.charAt(Math.floor(Math.random() * chars.length));
});
};
module.exports = async function(req, res) {
const dataDir = this.dataDir;
const interfacesFile = path.join(dataDir, 'interfaces.json');
const featuresFile = path.join(dataDir, 'features.json');
try {
// 只处理 POST 请求
if (req.method === 'POST') {
const { url, interfaceId } = req.body;
// 参数验证
if (!url) {
return res.status(400).json({
code: 400,
message: '测试URL不能为空',
data: null
});
}
if (!interfaceId) {
return res.status(400).json({
code: 400,
message: '接口ID不能为空',
data: null
});
}
// 读取接口数据
let interfacesData;
try {
interfacesData = fs.readJsonSync(interfacesFile);
if (!interfacesData || !interfacesData.interfaces) {
return res.status(404).json({
code: 404,
message: '未找到接口数据',
data: null
});
}
} catch (err) {
return res.status(500).json({
code: 500,
message: '读取接口数据失败',
data: null
});
}
// 查找指定接口
const targetInterface = interfacesData.interfaces.find(item => item.id === interfaceId);
if (!targetInterface) {
return res.status(404).json({
code: 404,
message: '指定的接口不存在',
data: null
});
}
// 检查接口是否已禁用
if (targetInterface.active === false) {
return res.status(400).json({
code: 400,
message: '接口已禁用,无法测试',
data: null
});
}
// 检查功能模块是否已禁用
try {
const featuresData = fs.readJsonSync(featuresFile);
if (featuresData && featuresData.features) {
const featureItem = featuresData.features.find(item => item.id === targetInterface.featureId);
if (featureItem && featureItem.active === false) {
return res.status(400).json({
code: 400,
message: '所属功能模块已禁用,接口无法使用',
data: null
});
}
}
} catch (err) {
// 忽略读取错误,继续处理
}
const startTime = Date.now();
// 根据代理类型处理不同的响应
let responseData;
// 检查 URL 是否匹配模式
const isUrlMatchPattern = (url, pattern, proxyType) => {
if (!url || !pattern) return false;
try {
// 对于url_redirect类型,需要完全匹配
if (proxyType === 'url_redirect') {
return url === pattern;
}
// 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
if (proxyType === 'redirect') {
return url.indexOf(pattern) === 0;
}
// 默认的匹配逻辑(用于response类型等)
// 如果是正则表达式
if (pattern.startsWith('/') && pattern.endsWith('/')) {
const regex = new RegExp(pattern.slice(1, -1));
return regex.test(url);
}
// 如果是通配符模式
if (pattern.includes('*')) {
const regexPattern = pattern
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(url);
}
// 精确匹配
return url === pattern;
} catch (e) {
console.error('URL匹配检查失败:', e);
return false;
}
};
// 处理不同的代理类型
switch (targetInterface.proxyType) {
case 'response':
// 处理内容,支持 Mock.js 模板语法
let responseBody = targetInterface.responseContent;
try {
// 如果响应内容是JSON,则尝试应用Mock.js模板
if (targetInterface.contentType && targetInterface.contentType.includes('json')) {
// 尝试解析JSON
const jsonTemplate = JSON.parse(responseBody);
// 使用Mock.js生成随机数据
const mockData = Mock.mock(jsonTemplate);
// 将结果转换回字符串
responseBody = JSON.stringify(mockData);
}
} catch (err) {
// 如果Mock失败,使用原始内容
}
// 模拟接口处理延迟
const delay = targetInterface.responseDelay || 0;
if (delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
const endTime = Date.now();
const duration = endTime - startTime;
// 模拟接口处理
responseData = {
statusCode: targetInterface.httpStatus || 200,
contentType: targetInterface.contentType || 'application/json; charset=utf-8',
responseBody: responseBody,
mockInfo: {
matchedPattern: targetInterface.urlPattern,
requestMethod: targetInterface.httpMethod,
delay: delay,
duration: duration,
timestamp: new Date().toISOString()
}
};
break;
case 'redirect':
// 处理重定向
const redirectTarget = targetInterface.targetUrl || '';
// 验证目标URL是否有效
if (!redirectTarget) {
return res.status(400).json({
code: 400,
message: '重定向目标URL不能为空',
data: null
});
}
try {
// 验证URL格式
new URL(redirectTarget);
} catch (err) {
return res.status(400).json({
code: 400,
message: '重定向目标URL格式无效,必须包含http://或https://',
data: null
});
}
// 验证URL匹配(前缀匹配)
if (url.indexOf(targetInterface.urlPattern) !== 0) {
return res.status(400).json({
code: 400,
message: `测试URL不匹配重定向规则,URL必须以 ${targetInterface.urlPattern} 开头`,
data: null
});
}
// 处理自定义请求头,包括随机值
const redirectHeaders = targetInterface.customHeaders || {};
const processedHeaders = {};
// 处理随机值,转换为实际值
Object.entries(redirectHeaders).forEach(([key, value]) => {
processedHeaders[key] = generateRandomValue(value);
});
const formattedRedirectHeaders = Object.entries(processedHeaders)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
responseData = {
statusCode: 302,
contentType: 'application/json; charset=utf-8',
proxyType: 'redirect',
targetUrl: redirectTarget,
customHeaders: processedHeaders,
formattedHeaders: formattedRedirectHeaders,
mockInfo: {
matchedPattern: targetInterface.urlPattern,
requestMethod: targetInterface.httpMethod,
timestamp: new Date().toISOString()
}
};
break;
case 'url_redirect':
// 处理URL重定向
const baseUrl = targetInterface.targetUrl || '';
// 验证基础URL是否有效
if (!baseUrl) {
return res.status(400).json({
code: 400,
message: 'URL重定向目标URL不能为空',
data: null
});
}
try {
// 验证URL格式
new URL(baseUrl);
} catch (err) {
return res.status(400).json({
code: 400,
message: 'URL重定向目标URL格式无效,必须包含http://或https://',
data: null
});
}
// 验证URL完全匹配
if (url !== targetInterface.urlPattern) {
return res.status(400).json({
code: 400,
message: `测试URL不匹配URL重定向规则,URL必须完全匹配 ${targetInterface.urlPattern}`,
data: null
});
}
// 处理自定义请求头
const urlRedirectHeaders = targetInterface.customHeaders || {};
const processedUrlRedirectHeaders = {};
// 处理随机值,转换为实际值
Object.entries(urlRedirectHeaders).forEach(([key, value]) => {
processedUrlRedirectHeaders[key] = generateRandomValue(value);
});
const formattedUrlRedirectHeaders = Object.entries(processedUrlRedirectHeaders)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
responseData = {
statusCode: 302,
contentType: 'application/json; charset=utf-8',
proxyType: 'url_redirect',
targetUrl: baseUrl,
customHeaders: processedUrlRedirectHeaders,
formattedHeaders: formattedUrlRedirectHeaders,
mockInfo: {
matchedPattern: targetInterface.urlPattern,
requestMethod: targetInterface.httpMethod,
timestamp: new Date().toISOString()
}
};
break;
default:
// 不支持的代理类型
return res.status(400).json({
code: 400,
message: `不支持的代理类型: ${targetInterface.proxyType || 'undefined'}`,
data: null
});
}
return res.json({
code: 0,
message: '成功',
data: responseData
});
} else {
// 不支持的请求方法
return res.status(405).json({
code: 405,
message: '不支持的请求方法',
data: null
});
}
} catch (err) {
return res.status(500).json({
code: 500,
message: '服务器内部错误: ' + err.message,
data: null
});
}
};