问题描述:
开发过的项目老是打不开,因为离开公司后服务器用不了了。所以想着在公司开发的时候把数据都备份一下,供之后参考项目代码。
实现方法:
建一个Express服务,前端请求Express,Express代理目标服务器,通过请求api以json格式缓存到本地mocks文件夹内。第一次请求时缓存下来,第二次直接用缓存。
- 初始化项目
创建一个新目录并初始化 package.json:
bash
mkdir mock-server
cd mock-server
npm init -y
安装必要的依赖:
bash
npm install express axios fs path url crypto
- 创建代理服务器代码
创建 server.js 文件,内容如下:
const express = require('express');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const url = require('url');
const crypto = require('crypto');
const app = express();
const MODE = 3; //1:根据路径生成缓存文件,2:根据路径和参数生成文件名,3:根据路径和参数和post 请求生成文件名
const PORT = 3000;
const TARGET_SERVER = 'http://192.168.0.184:6080'; // 替换为目标服务器地址
const MOCK_DIR = path.join(__dirname, 'mocks');
if (!fs.existsSync(MOCK_DIR)) {
fs.mkdirSync(MOCK_DIR);
}
app.use(express.json());
// 工具函数:对对象进行排序序列化,用于生成稳定 hash
function sortedStringify(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
// 如果是数组,则递归处理每一项
if (Array.isArray(obj)) {
return obj.map(item => sortedStringify(item));
}
const keys = Object.keys(obj).sort();
const sorted = {};
for (const k of keys) {
sorted[k] = sortedStringify(obj[k]);
}
return JSON.stringify(sorted);
}
// 工具函数:生成字符串的 hash,避免文件名过长
function getHash(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
// 通用代理中间件
app.use(async (req, res) => {
const parsedUrl = url.parse(req.url, true); // true 表示解析 query 参数
const { pathname, query } = parsedUrl;
// 构建缓存文件名:将 pathname 和 query 都作为标识
const queryParamsStr = Object.entries(query)
.sort(([a], [b]) => a.localeCompare(b)) // 排序确保 key 顺序一致(避免缓存碎片)
.map(([k, v]) => `${k}=${v}`)
.join('&');
let bodyHash = '';
let fileNameBase = ``;
let mockFilePath = ''
if (MODE === 1) {
mockFilePath = path.join(MOCK_DIR, `${pathname.replace(/\//g, '_')}.json`);
} else if (MODE === 2) {
fileNameBase = `${pathname.replace(/\//g, '_')}${queryParamsStr ? '_' + encodeURIComponent(queryParamsStr) : ''}`;
mockFilePath = path.join(MOCK_DIR, `${fileNameBase}.json`);
} else if (MODE === 3) {
const queryStr = queryParamsStr ? '_' + encodeURIComponent(queryParamsStr) : '';
if (req.method === 'POST' && req.body && Object.keys(req.body).length > 0) {
const bodyStr = sortedStringify(req.body);
bodyHash = '_' + getHash(bodyStr);
// console.log(45, bodyHash, req.body)
}
fileNameBase = `${pathname.replace(/\//g, '_')}${queryStr}${bodyHash}`;
mockFilePath = path.join(MOCK_DIR, `${fileNameBase}.json`);
}
// 检查本地是否存在缓存数据
if (fs.existsSync(mockFilePath)) {
const data = fs.readFileSync(mockFilePath, 'utf8');
try {
const jsonData = JSON.parse(data);
console.log(`返回缓存数据: ${pathname}`);
return res.json(jsonData);
} catch (e) {
console.error(`Failed to parse cached file: ${mockFilePath}`);
}
}
// 否则转发请求到目标服务器
try {
const targetUrl = `${TARGET_SERVER}${parsedUrl.path}`;
console.log('targetUrl:', targetUrl);
const response = await axios({
method: req.method,
url: targetUrl,
data: req.body,
headers: req.headers,
});
// 将响应数据缓存到本地
fs.writeFileSync(mockFilePath, JSON.stringify(response.data, null, 2), 'utf8');
console.log(`缓存服务器数据: ${pathname}`);
res.json(response.data);
} catch (error) {
console.error(`Proxy error for ${pathname}:`, error.message);
res.status(error.response?.status || 500).json({
error: error.message,
});
}
});
app.listen(PORT, () => {
console.log(`Mock server is running on http://localhost:${PORT}`);
});
注: 代码有3个模式,根据自己需要调。
1:根据路径生成缓存文件。如:get请求分页页面,pageNo传1和2只缓存1次。
2:根据路径和get的query参数生成缓存文件。如:get请求分页页面,pageNo传1和2缓存2次。
3:根据路径和参数和post 请求生成缓存文件(会缓存较多文件)。
- 使用方式
启动服务:
bash
node server.js
现在你本地运行了一个监听 http://localhost:3000 的代理服务器。
浏览器或前端请求示例:
将原本请求的目标 URL 改成:
http://localhost:3000/api/xxx
第一次请求会从真实服务器获取数据并缓存为文件(如:mocks/_api_xxx.json),后续请求直接使用本地缓存。
缓存文件: