增加 mock json
parent
8240a52a98
commit
2554bf8441
|
|
@ -13,6 +13,8 @@ RUN npm install
|
||||||
# 拷贝项目文件
|
# 拷贝项目文件
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# 启动服务(默认端口为 3000)
|
# 暴露端口
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mock-server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: mock-server
|
||||||
|
ports:
|
||||||
|
- "3002:3000"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
restart: unless-stopped
|
||||||
150
mock-config.json
150
mock-config.json
|
|
@ -33,17 +33,153 @@
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"path": "/api/mall/recommend",
|
"path": "/api/mall/recommend",
|
||||||
|
"query": { "type": "1" },
|
||||||
"response": {
|
"response": {
|
||||||
"products|4": [
|
"products": [
|
||||||
{
|
{
|
||||||
"id|+1": 1001,
|
"id": 1,
|
||||||
"name": "@pick(['老年精纺防尿湿纸尿裤 医用护理','防摔防滑地垫 卫生间适老化改造专用','多功能床边扶手 起夜支撑辅助器','老人专用加厚棉拖鞋'])",
|
"name": "老年精纺防尿湿纸尿裤 医用护理",
|
||||||
"price": "@float(39, 9999, 2, 2)",
|
"price": 120.0,
|
||||||
"image": "@image('200x200', '#f4f4f4', '商品图')",
|
"soldCount": 73,
|
||||||
"soldCount": "@natural(50, 999)"
|
"image": "https://res.klyj.jinzejk.com/mall/1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "防摔防滑地垫 卫生间适老化改造专用",
|
||||||
|
"price": 999.0,
|
||||||
|
"soldCount": 999,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/2.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "多功能床边扶手 起夜支撑辅助器",
|
||||||
|
"price": 888.9,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/3.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "老人专用加厚棉拖鞋 老人专用加厚棉拖...",
|
||||||
|
"price": 129.0,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/4.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/mall/recommend",
|
||||||
|
"query": { "type": "2" },
|
||||||
|
"response": {
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "防脱育发洗发水增密何发首乌生姜洗发液",
|
||||||
|
"price": 120.0,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/safe/1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "护理老人卧床病人用洗头神器",
|
||||||
|
"price": 9999.0,
|
||||||
|
"soldCount": 999,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/safe/2.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "硫磺液体香皂洗脸洗澡洗头祛痘止痒",
|
||||||
|
"price": 888.9,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/safe/3.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "防脱控油蓬松腺苷育发赋活洗发水",
|
||||||
|
"price": 120.0,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/safe/4.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/mall/recommend",
|
||||||
|
"query": { "type": "3" },
|
||||||
|
"response": {
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "轻奢碳纤维轻便登山拐杖老人防滑拐",
|
||||||
|
"price": 120.00,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/travel/1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "雅德助行器坐板累了可坐 可洗澡防水",
|
||||||
|
"price": 9999.00,
|
||||||
|
"soldCount": 999,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/travel/2.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "买菜小拉车快递车带盖轻便可折叠",
|
||||||
|
"price": 888.90,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/travel/3.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "老人拐杖凳子折叠便携多功能",
|
||||||
|
"price": 120.00,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/travel/4.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/mall/recommend",
|
||||||
|
"query": { "type": "4" },
|
||||||
|
"response": {
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "防脱育发洗发水增密何发首乌生姜洗发液",
|
||||||
|
"price": 120.00,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/care/1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "护理老人卧床病人用洗头神器",
|
||||||
|
"price": 9999.00,
|
||||||
|
"soldCount": 999,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/care/2.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "硫磺液体香皂洗脸洗澡洗头祛痘止痒",
|
||||||
|
"price": 888.90,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/care/3.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "防脱控油蓬松腺苷育发赋活洗发水",
|
||||||
|
"price": 120.00,
|
||||||
|
"soldCount": 73,
|
||||||
|
"image": "https://res.klyj.jinzejk.com/mall/care/4.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
|
@ -81,7 +217,7 @@
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"path": "/api/product/detail",
|
"path": "/api/product/detail",
|
||||||
"response": {
|
"response": {
|
||||||
"id": "1",
|
"id": 1,
|
||||||
"title": "防摔防滑地垫 卫生间适老化改造专用",
|
"title": "防摔防滑地垫 卫生间适老化改造专用",
|
||||||
"subtitle": "600g专用 600g专用 600g专用简介",
|
"subtitle": "600g专用 600g专用 600g专用简介",
|
||||||
"price": 120.0,
|
"price": 120.0,
|
||||||
|
|
|
||||||
106
server.js
106
server.js
|
|
@ -5,12 +5,11 @@ const fs = require('fs-extra');
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
const swaggerUi = require('swagger-ui-express');
|
const swaggerUi = require('swagger-ui-express');
|
||||||
const swaggerJSDoc = require('swagger-jsdoc');
|
const swaggerJSDoc = require('swagger-jsdoc');
|
||||||
const { Low, JSONFile } = require('lowdb');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
const CONFIG_PATH = './mock-config.json';
|
const CONFIG_PATH = './mock-config.json';
|
||||||
const DB_PATH = './mock-db.json';
|
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
|
@ -26,96 +25,50 @@ function formatResponse(data, code = 200, message = 'success') {
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerMockApi(config) {
|
function registerMockApi(config) {
|
||||||
const { method, path, response } = config;
|
const { method, path, response, query, swagger } = config;
|
||||||
const methodName = method.toLowerCase();
|
const methodName = method.toLowerCase();
|
||||||
|
|
||||||
app[methodName](path, async (req, res) => {
|
app[methodName](path, async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
// 地址模块逻辑
|
console.log(`[MOCK] Incoming: ${req.method} ${req.path}`, req.query);
|
||||||
if (path === '/api/user/address/list') {
|
|
||||||
await global.db.read();
|
// 默认 fallback
|
||||||
return res.json(formatResponse({ list: global.db.data.addresses }));
|
if (path === '/api/mall/recommend' && Object.keys(req.query).length === 0) {
|
||||||
|
const defaultConfig = global.mockConfigs.find(c =>
|
||||||
|
c.path === path && c.query?.type === 'home'
|
||||||
|
);
|
||||||
|
if (defaultConfig) {
|
||||||
|
console.log(`[MOCK] Fallback to default (home)`);
|
||||||
|
return res.json(formatResponse(defaultConfig.response));
|
||||||
}
|
}
|
||||||
if (path === '/api/user/address/add') {
|
|
||||||
const newItem = { id: Date.now(), ...req.body };
|
|
||||||
await global.db.read();
|
|
||||||
global.db.data.addresses.push(newItem);
|
|
||||||
await global.db.write();
|
|
||||||
return res.json(formatResponse({ id: newItem.id }, 200, '地址新增成功'));
|
|
||||||
}
|
|
||||||
if (path === '/api/user/address/update') {
|
|
||||||
const { id, ...rest } = req.body;
|
|
||||||
await global.db.read();
|
|
||||||
const item = global.db.data.addresses.find(i => i.id == id);
|
|
||||||
if (item) Object.assign(item, rest);
|
|
||||||
await global.db.write();
|
|
||||||
return res.json(formatResponse(null, 200, '地址已更新'));
|
|
||||||
}
|
|
||||||
if (path === '/api/user/address/delete') {
|
|
||||||
const { id } = req.body;
|
|
||||||
await global.db.read();
|
|
||||||
global.db.data.addresses = global.db.data.addresses.filter(i => i.id != id);
|
|
||||||
await global.db.write();
|
|
||||||
return res.json(formatResponse(null, 200, '地址已删除'));
|
|
||||||
}
|
|
||||||
if (path === '/api/user/address/set-default') {
|
|
||||||
const { id } = req.body;
|
|
||||||
await global.db.read();
|
|
||||||
global.db.data.addresses.forEach(i => i.isDefault = i.id == id);
|
|
||||||
await global.db.write();
|
|
||||||
return res.json(formatResponse(null, 200, '已设置为默认地址'));
|
|
||||||
}
|
|
||||||
if (path === '/api/user/address/detail') {
|
|
||||||
const { id } = req.query;
|
|
||||||
await global.db.read();
|
|
||||||
const item = global.db.data.addresses.find(i => i.id == id);
|
|
||||||
return res.json(formatResponse(item || null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他静态 mock 响应
|
// 精确 query 匹配
|
||||||
const data = Mock.mock(response);
|
if (query) {
|
||||||
res.json(formatResponse(data));
|
const isMatch = Object.entries(query).every(([key, val]) => req.query[key] === val);
|
||||||
|
if (!isMatch) return next(); // 继续往下找
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(formatResponse(response));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.json(formatResponse(null, 500, err.message));
|
res.json(formatResponse(null, 500, err.message));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
// 初始化 lowdb
|
|
||||||
const adapter = new JSONFile(DB_PATH);
|
|
||||||
const db = new Low(adapter);
|
|
||||||
await db.read();
|
|
||||||
db.data ||= { addresses: [], products: [], orders: [] };
|
|
||||||
await db.write();
|
|
||||||
global.db = db;
|
|
||||||
|
|
||||||
// 加载配置文件
|
|
||||||
|
// 读取配置
|
||||||
let mockConfigs = fs.existsSync(CONFIG_PATH) ? fs.readJsonSync(CONFIG_PATH) : [];
|
let mockConfigs = fs.existsSync(CONFIG_PATH) ? fs.readJsonSync(CONFIG_PATH) : [];
|
||||||
global.mockConfigs = mockConfigs;
|
global.mockConfigs = mockConfigs;
|
||||||
|
|
||||||
|
// 注册接口
|
||||||
mockConfigs.forEach(registerMockApi);
|
mockConfigs.forEach(registerMockApi);
|
||||||
|
|
||||||
// 添加 mock 接口
|
|
||||||
app.post('/mock-api/add', async (req, res) => {
|
|
||||||
const config = req.body;
|
|
||||||
if (!config.method || !config.path || !config.response) {
|
|
||||||
return res.json(formatResponse(null, 400, '参数不完整'));
|
|
||||||
}
|
|
||||||
registerMockApi(config);
|
|
||||||
global.mockConfigs.push(config);
|
|
||||||
await fs.writeJson(CONFIG_PATH, global.mockConfigs, { spaces: 2 });
|
|
||||||
res.json(formatResponse({ path: config.path }, 200, '接口已添加'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/mock-api/list', (req, res) => {
|
|
||||||
res.json(formatResponse(global.mockConfigs));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/mock-api/routes', (req, res) => {
|
|
||||||
const list = global.mockConfigs.map(({ method, path }) => ({ method, path }));
|
|
||||||
res.json(list);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// swagger 文档支持
|
||||||
const swaggerDefinition = {
|
const swaggerDefinition = {
|
||||||
openapi: '3.0.0',
|
openapi: '3.0.0',
|
||||||
info: {
|
info: {
|
||||||
|
|
@ -144,7 +97,12 @@ async function startServer() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
paths[cleanPath] = paths[cleanPath] || {};
|
paths[cleanPath] = paths[cleanPath] || {};
|
||||||
paths[cleanPath][methodName] = { summary, description, responses };
|
paths[cleanPath][methodName] = {
|
||||||
|
summary,
|
||||||
|
description,
|
||||||
|
parameters: swagger?.parameters || [],
|
||||||
|
responses
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return { ...swaggerDefinition, paths };
|
return { ...swaggerDefinition, paths };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue