diff --git a/Dockerfile b/Dockerfile index 0dc3e39..c961fcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ RUN npm install # 拷贝项目文件 COPY . . -# 启动服务(默认端口为 3000) +# 暴露端口 EXPOSE 3000 + +# 启动服务 CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1d9f80e --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/mock-config.json b/mock-config.json index d246a29..7c3e3d9 100644 --- a/mock-config.json +++ b/mock-config.json @@ -33,16 +33,152 @@ { "method": "GET", "path": "/api/mall/recommend", + "query": { "type": "1" }, "response": { - "products|4": [ + "products": [ + { + "id": 1, + "name": "老年精纺防尿湿纸尿裤 医用护理", + "price": 120.0, + "soldCount": 73, + "image": "https://res.klyj.jinzejk.com/mall/1.png" + }, { - "id|+1": 1001, - "name": "@pick(['老年精纺防尿湿纸尿裤 医用护理','防摔防滑地垫 卫生间适老化改造专用','多功能床边扶手 起夜支撑辅助器','老人专用加厚棉拖鞋'])", - "price": "@float(39, 9999, 2, 2)", - "image": "@image('200x200', '#f4f4f4', '商品图')", - "soldCount": "@natural(50, 999)" + "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" + } + ] + } }, { @@ -81,7 +217,7 @@ "method": "GET", "path": "/api/product/detail", "response": { - "id": "1", + "id": 1, "title": "防摔防滑地垫 卫生间适老化改造专用", "subtitle": "600g专用 600g专用 600g专用简介", "price": 120.0, diff --git a/server.js b/server.js index 0590255..a50fdc1 100644 --- a/server.js +++ b/server.js @@ -5,12 +5,11 @@ const fs = require('fs-extra'); const dayjs = require('dayjs'); const swaggerUi = require('swagger-ui-express'); const swaggerJSDoc = require('swagger-jsdoc'); -const { Low, JSONFile } = require('lowdb'); + const app = express(); const PORT = 3000; const CONFIG_PATH = './mock-config.json'; -const DB_PATH = './mock-db.json'; app.use(bodyParser.json()); @@ -26,96 +25,50 @@ function formatResponse(data, code = 200, message = 'success') { } function registerMockApi(config) { - const { method, path, response } = config; + const { method, path, response, query, swagger } = config; const methodName = method.toLowerCase(); - app[methodName](path, async (req, res) => { - try { - // 地址模块逻辑 - if (path === '/api/user/address/list') { - await global.db.read(); - return res.json(formatResponse({ list: global.db.data.addresses })); - } - 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)); - } + app[methodName](path, async (req, res, next) => { + try { + console.log(`[MOCK] Incoming: ${req.method} ${req.path}`, req.query); - // 其他静态 mock 响应 - const data = Mock.mock(response); - res.json(formatResponse(data)); - } catch (err) { - res.json(formatResponse(null, 500, err.message)); + // 默认 fallback + 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)); + } } - }); + + // 精确 query 匹配 + if (query) { + const isMatch = Object.entries(query).every(([key, val]) => req.query[key] === val); + if (!isMatch) return next(); // 继续往下找 + } + + return res.json(formatResponse(response)); + } catch (err) { + res.json(formatResponse(null, 500, err.message)); + } +}); } 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) : []; global.mockConfigs = mockConfigs; + + // 注册接口 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 = { openapi: '3.0.0', info: { @@ -144,7 +97,12 @@ async function startServer() { } }; paths[cleanPath] = paths[cleanPath] || {}; - paths[cleanPath][methodName] = { summary, description, responses }; + paths[cleanPath][methodName] = { + summary, + description, + parameters: swagger?.parameters || [], + responses + }; }); return { ...swaggerDefinition, paths }; }