const express = require('express'); const bodyParser = require('body-parser'); const Mock = require('mockjs'); const fs = require('fs-extra'); const dayjs = require('dayjs'); const swaggerUi = require('swagger-ui-express'); const swaggerJSDoc = require('swagger-jsdoc'); const app = express(); const PORT = 3000; const CONFIG_PATH = './mock-config.json'; app.use(bodyParser.json()); function formatResponse(data, code = 200, message = 'success') { return { code, type: code === 200 ? 'success' : 'error', message, result: data, extras: null, time: dayjs().format('YYYY-MM-DD HH:mm:ss') }; } function registerMockApi(config) { const { method, path, response, query, swagger } = config; const methodName = method.toLowerCase(); app[methodName](path, async (req, res, next) => { try { console.log(`[MOCK] Incoming: ${req.method} ${req.path}`, req.query); // 默认 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() { // 读取配置 let mockConfigs = fs.existsSync(CONFIG_PATH) ? fs.readJsonSync(CONFIG_PATH) : []; global.mockConfigs = mockConfigs; // 注册接口 mockConfigs.forEach(registerMockApi); // swagger 文档支持 const swaggerDefinition = { openapi: '3.0.0', info: { title: 'Mock API Docs', version: '1.0.0', description: 'Dynamically registered mock APIs' }, servers: [] }; function buildSwaggerSpecFromMocks(configs) { const paths = {}; configs.forEach(({ method, path, swagger }) => { const cleanPath = path.replace(/:([^/]+)/g, '{$1}'); const methodName = method.toLowerCase(); const summary = swagger?.summary || `Mocked ${method} ${path}`; const description = swagger?.description || ''; const responses = swagger?.responses || { 200: { description: 'Mock response', content: { 'application/json': { schema: { type: 'object' } } } } }; paths[cleanPath] = paths[cleanPath] || {}; paths[cleanPath][methodName] = { summary, description, parameters: swagger?.parameters || [], responses }; }); return { ...swaggerDefinition, paths }; } app.use('/swagger', (req, res, next) => { res.setHeader('Cache-Control', 'no-store'); next(); }); app.use('/swagger', swaggerUi.serve, (req, res, next) => { const protocol = req.headers['x-forwarded-proto'] || req.protocol; const host = req.get('host'); const dynamicSwaggerDefinition = { ...swaggerDefinition, servers: [ { url: `${protocol}://${host}`, description: 'Current host' } ] }; const spec = buildSwaggerSpecFromMocks(global.mockConfigs); const dynamicSpec = { ...spec, servers: dynamicSwaggerDefinition.servers }; swaggerUi.setup(dynamicSpec)(req, res, next); }); app.listen(PORT, () => { console.log(`✅ Mock Server is running at http://localhost:${PORT}`); console.log(`📚 Swagger UI available at http://localhost:${PORT}/swagger`); }); } startServer();