whistle.mock-plugins
Version:
Whistle 插件,用于快速创建 API 模拟数据
343 lines (293 loc) • 10.3 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const Mock = require('mockjs');
const url = require('url');
// 缓存控制
const CONFIG = {
// 缓存有效期(毫秒),默认 60 秒
CACHE_INTERVAL: process.env.MOCK_PLUGIN_CACHE_INTERVAL || 60000
};
// 缓存控制变量
let enabledInterfacesCache = null; // 仅缓存启用状态的接口
let cacheTime = 0;
// 获取文件修改时间
const getFileModTime = (filePath) => {
try {
const stats = fs.statSync(filePath);
return stats.mtimeMs;
} catch (err) {
return 0;
}
};
// 生成随机数工具函数(与rules-server.js中保持一致)
const generateRandomValue = (pattern) => {
// 如果不是以@开头的模式,直接返回原值
if (!pattern || !pattern.startsWith('@')) {
return pattern;
}
const formatPattern = pattern.substring(1); // 去掉@前缀
// 使用单次正则替换优化性能
return formatPattern.replace(/x/g, () => {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return chars.charAt(Math.floor(Math.random() * chars.length));
});
};
// 正则表达式缓存
const regexCache = new Map();
// URL匹配函数,提取为纯函数减少重复代码
const isUrlMatchPattern = (url, pattern, proxyType) => {
if (!url || !pattern) return false;
console.log(`isUrlMatchPattern ${url} - ${pattern}`);
try {
// 对于url_redirect类型,需要完全匹配
if (proxyType === 'url_redirect') {
return url === pattern;
}
// 对于redirect类型,只要url以pattern开头即可命中(前缀匹配)
if (proxyType === 'redirect') {
return url.startsWith(pattern);
}
// 默认的匹配逻辑(用于response类型等)
// 精确匹配
if (pattern === url) {
return true;
}
// 正则表达式
if (pattern.startsWith('/') && pattern.length > 2 && pattern.endsWith('/')) {
// 使用缓存的正则表达式
const cacheKey = `regex:${pattern}`;
let regex = regexCache.get(cacheKey);
if (!regex) {
regex = new RegExp(pattern.slice(1, -1));
regexCache.set(cacheKey, regex);
}
return regex.test(url);
}
// 通配符模式
if (pattern.includes('*')) {
// 使用缓存的正则表达式
const cacheKey = `wildcard:${pattern}`;
let regex = regexCache.get(cacheKey);
if (!regex) {
const regexPattern = pattern
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
regex = new RegExp(`^${regexPattern}$`);
regexCache.set(cacheKey, regex);
}
return regex.test(url);
}
return false;
} catch (e) {
console.error('URL匹配检查失败:', e);
return false;
}
};
// 加载启用的接口数据,带缓存
const loadEnabledInterfaces = (dataDir, interfacesFile, featuresFile) => {
try {
const currentTime = Date.now();
const interfacesFileModTime = getFileModTime(interfacesFile);
const featuresFileModTime = getFileModTime(featuresFile);
// 取两个文件的最后修改时间的最大值
const lastModTime = Math.max(interfacesFileModTime, featuresFileModTime);
// 使用缓存
if (enabledInterfacesCache &&
currentTime - cacheTime < CONFIG.CACHE_INTERVAL &&
lastModTime <= cacheTime) {
return enabledInterfacesCache;
}
// 1. 读取并解析所有功能模块
let enabledFeatureIds = [];
if (fs.existsSync(featuresFile)) {
try {
const featuresData = fs.readJsonSync(featuresFile);
let features = [];
if (featuresData && Array.isArray(featuresData)) {
features = featuresData;
} else if (featuresData && featuresData.features && Array.isArray(featuresData.features)) {
features = featuresData.features;
}
// 过滤出启用状态的功能ID
enabledFeatureIds = features
.filter(feature => feature.active === true)
.map(feature => feature.id);
} catch (err) {
console.error('读取功能模块失败:', err);
}
}
// 2. 读取并解析所有接口
let enabledInterfaces = [];
if (fs.existsSync(interfacesFile)) {
try {
const interfacesData = fs.readJsonSync(interfacesFile);
let interfaces = [];
if (interfacesData && Array.isArray(interfacesData)) {
interfaces = interfacesData;
} else if (interfacesData && interfacesData.interfaces && Array.isArray(interfacesData.interfaces)) {
interfaces = interfacesData.interfaces;
}
// 过滤出启用状态的接口
enabledInterfaces = interfaces.filter(intf => {
// 首先检查接口本身是否启用
const isInterfaceEnabled = intf.active !== false; // 默认为true
if (!isInterfaceEnabled) {
return false;
}
// 如果接口有关联功能,检查功能是否启用
if (intf.featureId) {
return enabledFeatureIds.includes(intf.featureId);
}
// 如果接口没有关联功能,只要接口本身启用就可以
return true;
});
} catch (err) {
console.error('读取接口配置失败:', err);
}
}
// 更新缓存
enabledInterfacesCache = enabledInterfaces;
cacheTime = currentTime;
return enabledInterfaces;
} catch (err) {
console.error('加载启用接口失败:', err);
return enabledInterfacesCache || [];
}
};
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
});
}
// 读取接口数据(使用缓存)
const interfaces = loadEnabledInterfaces(dataDir, interfacesFile, featuresFile);
if (!interfaces || interfaces.length === 0) {
return res.status(404).json({
code: 404,
message: '未找到接口数据',
data: null
});
}
// 查找指定接口
const targetInterface = interfaces.find(item => item.id === interfaceId);
if (!targetInterface) {
return res.status(404).json({
code: 404,
message: '指定的接口不存在',
data: null
});
}
// 检查接口URL是否与测试URL匹配
const pattern = targetInterface.pattern;
const proxyType = targetInterface.proxyType || 'response';
// 使用URL匹配检查
const isMatch = isUrlMatchPattern(url, pattern, proxyType);
// 测试接口只验证URL模式匹配,不验证HTTP方法
// 因为这里的req.method是测试请求的POST方法,与接口配置的HTTP方法无关
const matchResult = isMatch;
// 构建测试结果
const result = {
interfaceId: targetInterface.id,
interfaceName: targetInterface.name,
proxyType: targetInterface.proxyType,
pattern: targetInterface.pattern,
url: url,
isMatch: matchResult,
matchDetail: {
urlMatch: isMatch,
note: 'HTTP方法匹配在测试模式下不进行验证,仅验证URL模式匹配'
}
};
// 根据代理类型处理额外信息
if (matchResult) {
if (proxyType === 'response') {
// 添加响应数据(如果有)
if (targetInterface.response) {
result.responseData = targetInterface.response;
}
} else if (proxyType === 'redirect' || proxyType === 'url_redirect') {
// 添加重定向信息(如果有)
if (targetInterface.targetUrl) {
// 处理动态生成的目标URL
const targetUrl = generateRandomValue(targetInterface.targetUrl);
result.redirectInfo = {
targetUrl: targetUrl,
targetFormatted: targetUrl
};
// 添加自定义请求头(如果有)
if (targetInterface.customHeaders) {
try {
result.redirectInfo.customHeaders = targetInterface.customHeaders;
} catch (e) {
console.error('解析自定义请求头失败:', e);
}
}
}
}
}
// 返回测试结果
return res.json({
code: 200,
message: matchResult ? '接口匹配成功' : '接口匹配失败',
data: result
});
}
// 处理缓存刷新请求
if (req.path === '/_flush_cache') {
// 清空缓存
enabledInterfacesCache = null;
cacheTime = 0;
return res.json({
code: 200,
message: '缓存已刷新',
data: null
});
}
// 处理缓存状态请求
if (req.path === '/_cache_status') {
return res.json({
code: 200,
message: '缓存状态',
data: {
enabledInterfaces: {
cached: !!enabledInterfacesCache,
lastUpdate: cacheTime,
count: enabledInterfacesCache ? enabledInterfacesCache.length : 0
},
cacheInterval: CONFIG.CACHE_INTERVAL
}
});
}
// 其他请求返回405
return res.status(405).json({
code: 405,
message: '方法不允许',
data: null
});
} catch (err) {
console.error('测试接口处理错误:', err);
return res.status(500).json({
code: 500,
message: '服务器内部错误: ' + (err.message || err),
data: null
});
}
};