代理服务器-MOCK
This commit is contained in:
commit
9b427b8fa0
317
index.api.ts
Normal file
317
index.api.ts
Normal file
@ -0,0 +1,317 @@
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const proxyPort = 9443;
|
||||
const targetHost = "devrmtapp.resmart.cn";
|
||||
|
||||
// 配置文件路径
|
||||
const CONFIG_FILE = path.join(__dirname, 'mock-routes.json');
|
||||
|
||||
// 定义类型
|
||||
interface RouteConfig {
|
||||
[route: string]: string;
|
||||
}
|
||||
|
||||
interface AppConfig {
|
||||
cacheConfig: boolean;
|
||||
reloadOnChange: boolean;
|
||||
defaultContentType: string;
|
||||
}
|
||||
|
||||
interface ConfigFile {
|
||||
routes: RouteConfig;
|
||||
config: AppConfig;
|
||||
}
|
||||
|
||||
// 存储当前的路由配置
|
||||
let MOCK_ROUTES: RouteConfig = {};
|
||||
let CONFIG: AppConfig = {
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
defaultContentType: "application/json"
|
||||
};
|
||||
|
||||
// 过滤掉以 # 开头的路由(视为注释,不参与 mock)
|
||||
function filterActiveRoutes(routes: RouteConfig): RouteConfig {
|
||||
const filtered: RouteConfig = {};
|
||||
for (const [route, filePath] of Object.entries(routes)) {
|
||||
if (route.startsWith('#')) continue;
|
||||
filtered[route] = filePath;
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// 加载配置文件
|
||||
function loadConfig() {
|
||||
try {
|
||||
if (!fs.existsSync(CONFIG_FILE)) {
|
||||
console.warn(`[CONFIG] 配置文件不存在: ${CONFIG_FILE}`);
|
||||
console.warn(`[CONFIG] 正在创建默认配置文件...`);
|
||||
|
||||
// 创建默认配置
|
||||
const defaultConfig: ConfigFile = {
|
||||
routes: {
|
||||
"/api2/test": "mock/test.txt"
|
||||
},
|
||||
config: {
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
defaultContentType: "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
// 确保mock目录存在
|
||||
const mockDir = path.join(__dirname, 'mock');
|
||||
if (!fs.existsSync(mockDir)) {
|
||||
fs.mkdirSync(mockDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 保存配置文件
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2), 'utf-8');
|
||||
console.log(`[CONFIG] 已创建默认配置文件: ${CONFIG_FILE}`);
|
||||
|
||||
// 加载配置(# 开头的路由视为注释,不参与 mock)
|
||||
const configData: ConfigFile = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
||||
MOCK_ROUTES = filterActiveRoutes(configData.routes || {});
|
||||
CONFIG = configData.config || CONFIG;
|
||||
} else {
|
||||
const configData: ConfigFile = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
||||
MOCK_ROUTES = filterActiveRoutes(configData.routes || {});
|
||||
CONFIG = configData.config || CONFIG;
|
||||
console.log(`[CONFIG] 配置文件已加载,共 ${Object.keys(MOCK_ROUTES).length} 个路由`);
|
||||
}
|
||||
|
||||
// 验证mock文件是否存在
|
||||
validateMockFiles();
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[CONFIG] 加载配置文件失败:`, error);
|
||||
// 使用默认配置
|
||||
MOCK_ROUTES = {
|
||||
"/api2/user/list": "mock/user_list.txt"
|
||||
};
|
||||
CONFIG = {
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
defaultContentType: "application/json"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 验证mock文件是否存在
|
||||
function validateMockFiles() {
|
||||
console.log(`[CONFIG] 验证mock文件...`);
|
||||
let missingFiles = [];
|
||||
|
||||
for (const [route, filePath] of Object.entries(MOCK_ROUTES)) {
|
||||
const fullPath = path.join(__dirname, filePath);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
missingFiles.push({ route, filePath, fullPath });
|
||||
console.warn(`[CONFIG] 警告: mock文件不存在 - ${filePath} (用于路由: ${route})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFiles.length > 0) {
|
||||
console.log(`[CONFIG] 缺少 ${missingFiles.length} 个mock文件,请创建这些文件`);
|
||||
} else {
|
||||
console.log(`[CONFIG] 所有mock文件验证通过`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否为mock路由的函数
|
||||
function isMockRoute(requestPath: string): boolean {
|
||||
return MOCK_ROUTES.hasOwnProperty(requestPath);
|
||||
}
|
||||
|
||||
// 获取mock文件路径
|
||||
function getMockFilePath(requestPath: string): string {
|
||||
return MOCK_ROUTES[requestPath];
|
||||
}
|
||||
|
||||
// 代理服务器
|
||||
const proxyServer = http.createServer((clientReq, clientRes) => {
|
||||
// 解析客户端请求的 URL
|
||||
const parsedUrl = new URL(`http://localhost${clientReq.url!}`);
|
||||
const requestPath = parsedUrl.pathname;
|
||||
|
||||
// 检查是否为需要mock的路由
|
||||
if (isMockRoute(requestPath)) {
|
||||
const mockFile = getMockFilePath(requestPath);
|
||||
console.log(`[MOCK] 拦截路由: ${requestPath} -> 使用文件: ${mockFile}`);
|
||||
|
||||
try {
|
||||
// 构建完整的文件路径
|
||||
const mockFilePath = path.join(__dirname, mockFile);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(mockFilePath)) {
|
||||
console.error(`[MOCK] Mock文件不存在: ${mockFilePath}`);
|
||||
clientRes.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
clientRes.end(JSON.stringify({
|
||||
error: 'Mock file not found',
|
||||
route: requestPath,
|
||||
file: mockFile,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取本地mock文件
|
||||
const mockData = fs.readFileSync(mockFilePath, 'utf-8');
|
||||
|
||||
// 设置响应头
|
||||
const contentType = CONFIG.defaultContentType || 'application/json';
|
||||
clientRes.writeHead(200, {
|
||||
'Content-Type': contentType,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'X-Mock-Source': mockFile,
|
||||
'X-Mock-Timestamp': new Date().toISOString()
|
||||
});
|
||||
|
||||
// 返回mock数据
|
||||
clientRes.end(mockData);
|
||||
console.log(`[MOCK] 成功返回mock数据: ${mockFile}`);
|
||||
} catch (error) {
|
||||
console.error(`[MOCK] 读取mock文件失败: ${mockFile}`, error);
|
||||
clientRes.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
clientRes.end(JSON.stringify({
|
||||
error: 'Failed to read mock data',
|
||||
route: requestPath,
|
||||
file: mockFile,
|
||||
message: (error as Error).message,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
}
|
||||
return; // 直接返回,不转发到目标服务器
|
||||
}
|
||||
|
||||
// 如果不是mock路由,正常代理转发
|
||||
console.log(`[PROXY] 转发请求: ${requestPath}`);
|
||||
|
||||
// 目标服务器的选项
|
||||
const options: http.RequestOptions = {
|
||||
hostname: targetHost,
|
||||
port: 443, // HTTPS 默认端口
|
||||
method: clientReq.method,
|
||||
path: parsedUrl.pathname + parsedUrl.search,
|
||||
headers: {
|
||||
...clientReq.headers,
|
||||
host: targetHost
|
||||
}
|
||||
};
|
||||
|
||||
// 创建到目标服务器的 HTTPS 请求
|
||||
const proxyReq = https.request(options, (proxyRes) => {
|
||||
// 将目标服务器的响应头复制到客户端响应
|
||||
clientRes.writeHead(proxyRes.statusCode!, proxyRes.headers);
|
||||
// 将目标服务器的响应数据管道传输到客户端
|
||||
proxyRes.pipe(clientRes);
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
proxyReq.on('error', (err) => {
|
||||
console.error('Proxy request error:', err);
|
||||
clientRes.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
clientRes.end(JSON.stringify({
|
||||
error: 'Proxy error',
|
||||
message: err.message,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
});
|
||||
|
||||
// 将客户端请求体管道传输到代理请求
|
||||
clientReq.pipe(proxyReq);
|
||||
});
|
||||
|
||||
// 添加管理接口用于重新加载配置
|
||||
proxyServer.on('request', (req, res) => {
|
||||
const parsedUrl = new URL(`http://localhost${req.url!}`);
|
||||
const pathname = parsedUrl.pathname;
|
||||
|
||||
// 管理接口:重新加载配置
|
||||
if (pathname === '/__reload-config' && req.method === 'POST') {
|
||||
try {
|
||||
const oldCount = Object.keys(MOCK_ROUTES).length;
|
||||
loadConfig();
|
||||
const newCount = Object.keys(MOCK_ROUTES).length;
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
message: 'Configuration reloaded successfully',
|
||||
routesCount: newCount,
|
||||
routesChanged: newCount - oldCount
|
||||
}));
|
||||
console.log(`[ADMIN] 通过API重新加载配置 (路由数: ${oldCount} -> ${newCount})`);
|
||||
} catch (error) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 管理接口:查看当前配置
|
||||
if (pathname === '/__config' && req.method === 'GET') {
|
||||
try {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
routes: MOCK_ROUTES,
|
||||
config: CONFIG,
|
||||
timestamp: new Date().toISOString(),
|
||||
totalRoutes: Object.keys(MOCK_ROUTES).length
|
||||
}, null, 2));
|
||||
} catch (error) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化:第一次加载配置
|
||||
loadConfig();
|
||||
|
||||
// 如果配置了文件监视,则监听配置文件变化
|
||||
if (CONFIG.reloadOnChange) {
|
||||
fs.watchFile(CONFIG_FILE, (curr, prev) => {
|
||||
console.log(`[CONFIG] 配置文件已修改,重新加载...`);
|
||||
try {
|
||||
const oldRoutesCount = Object.keys(MOCK_ROUTES).length;
|
||||
loadConfig();
|
||||
const newRoutesCount = Object.keys(MOCK_ROUTES).length;
|
||||
console.log(`[CONFIG] 配置重载完成 (路由数: ${oldRoutesCount} -> ${newRoutesCount})`);
|
||||
} catch (error) {
|
||||
console.error(`[CONFIG] 重新加载配置文件失败:`, error);
|
||||
}
|
||||
});
|
||||
console.log(`[CONFIG] 已启用配置文件监视: ${CONFIG_FILE}`);
|
||||
}
|
||||
|
||||
proxyServer.listen(proxyPort, "0.0.0.0", () => {
|
||||
console.log(`========================================`);
|
||||
console.log(`代理服务器运行在: http://localhost:${proxyPort}`);
|
||||
console.log(`目标服务器: https://${targetHost}`);
|
||||
console.log(`配置文件: ${CONFIG_FILE}`);
|
||||
console.log(`已配置Mock路由: ${Object.keys(MOCK_ROUTES).length} 个`);
|
||||
console.log(`========================================`);
|
||||
console.log(`管理接口:`);
|
||||
console.log(` GET http://localhost:${proxyPort}/__config 查看当前配置`);
|
||||
console.log(` POST http://localhost:${proxyPort}/__reload-config 重新加载配置`);
|
||||
console.log(`========================================`);
|
||||
console.log(`Mock路由列表:`);
|
||||
|
||||
for (const [route, file] of Object.entries(MOCK_ROUTES)) {
|
||||
const filePath = path.join(__dirname, file);
|
||||
const exists = fs.existsSync(filePath) ? '✓' : '✗';
|
||||
console.log(` ${exists} ${route} -> ${file}`);
|
||||
}
|
||||
console.log(`========================================`);
|
||||
});
|
||||
18
mock-routes.json
Normal file
18
mock-routes.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"routes": {
|
||||
"#/api2/test": "mock/test.txt",
|
||||
"#/api2/test2": "mock/test2.txt",
|
||||
"#/api2/oxygenerator/report/day": "mock/oxygenerator_day.txt",
|
||||
"#/api2/oxygenerator/report/multi/day": "mock/oxygenerator_days.txt",
|
||||
"#/api2/ventilator/report/day" :"mock/ventilator_day.txt",
|
||||
"#/api2/ventilator/report/multi/day" :"mock/ventilator_days.txt",
|
||||
"#/api2/device/history/list" :"mock/device_list.txt",
|
||||
"#/api2/health/service/getById" :"mock/service_failed.txt",
|
||||
"#/api2/device/one": "mock/device_detail.txt"
|
||||
},
|
||||
"config": {
|
||||
"cacheConfig": true,
|
||||
"reloadOnChange": true,
|
||||
"defaultContentType": "application/json"
|
||||
}
|
||||
}
|
||||
19
mock/device_detail.txt
Normal file
19
mock/device_detail.txt
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"serial": "A3120991235",
|
||||
"id": 1112,
|
||||
"name": "家用无创呼吸机",
|
||||
"buyTime": 1773331200000,
|
||||
"model": "G3 A20",
|
||||
"bindTime": 1773381129000,
|
||||
"lastUseTime": null,
|
||||
"imageFile": {
|
||||
"objKey": "test/bmc-file/common/a119dce5-84af-4a19-bbca-72bf2e5419e8.jpg",
|
||||
"bucket": "new-c",
|
||||
"mimeType": "image/jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
48
mock/device_list.txt
Normal file
48
mock/device_list.txt
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"success": true,
|
||||
"data": [{
|
||||
"serial": "A3120991234",
|
||||
"id": 1149,
|
||||
"name": "家用无创呼吸机",
|
||||
"buyTime": 1773244800000,
|
||||
"provinceId": 1,
|
||||
"cityId": 2,
|
||||
"buyChannel": 1,
|
||||
"storeName": "干撒干撒",
|
||||
"model": "G3 A20",
|
||||
"storeId": null,
|
||||
"bindTime": 1773302358000,
|
||||
"lastUseTime": null,
|
||||
"thumbnailFile": {
|
||||
"objKey": "dev/bmc-file/common/2f4b2f40-e15c-42a4-90e6-1521a64df8e7.jpg",
|
||||
"bucket": "new-c",
|
||||
"mimeType": "image/jpeg"
|
||||
},
|
||||
"devicePageFile": null,
|
||||
"deviceType": 3,
|
||||
"nonProtocolDevice": true,
|
||||
"ventilatorType": null,
|
||||
"bluetooth": null
|
||||
}, {
|
||||
"serial": "AS124C01084",
|
||||
"id": 1148,
|
||||
"name": "家用无创呼吸机",
|
||||
"buyTime": 1773244800000,
|
||||
"provinceId": 1,
|
||||
"cityId": 2,
|
||||
"buyChannel": 1,
|
||||
"storeName": "哈哈",
|
||||
"model": "恬梦 A20",
|
||||
"storeId": null,
|
||||
"bindTime": 1773302335000,
|
||||
"lastUseTime": null,
|
||||
"thumbnailFile": null,
|
||||
"devicePageFile": null,
|
||||
"deviceType": 3,
|
||||
"nonProtocolDevice": true,
|
||||
"ventilatorType": null,
|
||||
"bluetooth": null
|
||||
}]
|
||||
}
|
||||
63
mock/oxygenerator_day.txt
Normal file
63
mock/oxygenerator_day.txt
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
|
||||
"code": 0,
|
||||
|
||||
"msg": "success",
|
||||
|
||||
"success": true,
|
||||
|
||||
"data": {
|
||||
|
||||
"timeList": [
|
||||
|
||||
{
|
||||
|
||||
"remark": "短时佩戴 (30秒)",
|
||||
|
||||
"startDate": 1772154000000,
|
||||
|
||||
"endDate": 1772154030000
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"remark": "午后记录 (45分钟)",
|
||||
|
||||
"startDate": 1772172000000,
|
||||
|
||||
"endDate": 1772174700000
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"remark": "晚上记录 (70分钟)",
|
||||
|
||||
"startDate": 1772193600000,
|
||||
|
||||
"endDate": 1772197800000
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"baseInfo": {
|
||||
|
||||
"useDate": 1772121600000,
|
||||
|
||||
"useTime": 60,
|
||||
|
||||
"avgOxygenation": "97.5",
|
||||
|
||||
"respiratoryRate": 18,
|
||||
|
||||
"maxRespiratoryRate": 24,
|
||||
|
||||
"minRespiratoryRate": 10
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
29
mock/oxygenerator_days.txt
Normal file
29
mock/oxygenerator_days.txt
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"timeList": [{
|
||||
"useDate": 1769270400000,
|
||||
"useTime": 110
|
||||
}, {
|
||||
"useDate": 1769184000000,
|
||||
"useTime": 1000
|
||||
}],
|
||||
"baseInfoList": [{
|
||||
"useDate": 1769270400000,
|
||||
"useTime": 100,
|
||||
"avgOxygenation": "10.0",
|
||||
"respiratoryRate": 50,
|
||||
"maxRespiratoryRate": 20,
|
||||
"minRespiratoryRate": 1
|
||||
}, {
|
||||
"useDate": 1769184000000,
|
||||
"useTime": 100,
|
||||
"avgOxygenation": "10.0",
|
||||
"respiratoryRate": 50,
|
||||
"maxRespiratoryRate": 20,
|
||||
"minRespiratoryRate": 1
|
||||
}]
|
||||
}
|
||||
}
|
||||
6
mock/service_failed.txt
Normal file
6
mock/service_failed.txt
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"code": -1,
|
||||
"msg": "failed",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
1
mock/test.txt
Normal file
1
mock/test.txt
Normal file
@ -0,0 +1 @@
|
||||
测试
|
||||
1
mock/test2.txt
Normal file
1
mock/test2.txt
Normal file
@ -0,0 +1 @@
|
||||
23432423423423
|
||||
345949
mock/ventilator_day.txt
Normal file
345949
mock/ventilator_day.txt
Normal file
File diff suppressed because it is too large
Load Diff
1157
mock/ventilator_days.txt
Normal file
1157
mock/ventilator_days.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user