UNPKG

whistle.mock-plugins

Version:

Whistle 插件,用于快速创建 API 模拟数据

239 lines (200 loc) 6.92 kB
const express = require('express'); const fs = require('fs-extra'); const path = require('path'); const Mock = require('mockjs'); const storage = require('../storage'); /** * 代理路由处理程序 * 负责处理文件和 Mock 数据相关的 API 请求 */ const FILES_DIR = storage.FILES_DIR; // 确保文件目录存在 fs.ensureDirSync(FILES_DIR); // 初始化路由器 const router = express.Router(); // 解析 JSON 请求体 router.use(express.json({ limit: '100mb' })); /** * 获取文件内容 * GET /api/proxy/file?path=example.json */ router.get('/proxy/file', (req, res) => { try { const filePath = req.query.path; if (!filePath) { return res.status(400).json({ error: '缺少文件路径参数' }); } // 处理相对路径和绝对路径 const fullPath = path.isAbsolute(filePath) ? filePath : path.join(FILES_DIR, filePath); // 确保路径安全(防止目录遍历攻击) const normalizedPath = path.normalize(fullPath); if (!normalizedPath.startsWith(FILES_DIR) && !path.isAbsolute(filePath)) { return res.status(403).json({ error: '访问被拒绝:路径超出允许范围' }); } if (!fs.existsSync(normalizedPath)) { return res.status(404).json({ error: '文件不存在' }); } if (fs.statSync(normalizedPath).isDirectory()) { return res.status(400).json({ error: '不能读取目录,请指定文件' }); } const content = fs.readFileSync(normalizedPath, 'utf-8'); const stats = fs.statSync(normalizedPath); res.json({ path: filePath, name: path.basename(filePath), content, size: stats.size, mtime: stats.mtime, isDirectory: false }); } catch (err) { console.error('获取文件内容失败:', err); res.status(500).json({ error: '获取文件内容失败: ' + err.message }); } }); /** * 保存文件内容 * POST /api/proxy/file * body: { path: 'example.json', content: '{"key": "value"}' } */ router.post('/proxy/file', (req, res) => { try { const { path: filePath, content } = req.body; if (!filePath) { return res.status(400).json({ error: '缺少文件路径参数' }); } if (content === undefined) { return res.status(400).json({ error: '缺少文件内容参数' }); } // 处理相对路径和绝对路径 const fullPath = path.isAbsolute(filePath) ? filePath : path.join(FILES_DIR, filePath); // 确保路径安全(防止目录遍历攻击) const normalizedPath = path.normalize(fullPath); if (!normalizedPath.startsWith(FILES_DIR) && !path.isAbsolute(filePath)) { return res.status(403).json({ error: '访问被拒绝:路径超出允许范围' }); } // 确保目录存在 fs.ensureDirSync(path.dirname(normalizedPath)); // 写入文件 fs.writeFileSync(normalizedPath, content, 'utf-8'); const stats = fs.statSync(normalizedPath); res.json({ path: filePath, name: path.basename(filePath), size: stats.size, mtime: stats.mtime, isDirectory: false }); } catch (err) { console.error('保存文件内容失败:', err); res.status(500).json({ error: '保存文件内容失败: ' + err.message }); } }); /** * 获取文件列表 * GET /api/proxy/files?path=directory */ router.get('/proxy/files', (req, res) => { try { const dirPath = req.query.path || ''; // 处理相对路径和绝对路径 const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(FILES_DIR, dirPath); // 确保路径安全(防止目录遍历攻击) const normalizedPath = path.normalize(fullPath); if (!normalizedPath.startsWith(FILES_DIR) && !path.isAbsolute(dirPath)) { return res.status(403).json({ error: '访问被拒绝:路径超出允许范围' }); } // 确保目录存在 if (!fs.existsSync(normalizedPath)) { fs.ensureDirSync(normalizedPath); } if (!fs.statSync(normalizedPath).isDirectory()) { return res.status(400).json({ error: '指定路径不是目录' }); } const files = fs.readdirSync(normalizedPath) .filter(file => !file.startsWith('.') || file === '.placeholder') .map(file => { const filePath = path.join(normalizedPath, file); const stats = fs.statSync(filePath); const isDirectory = stats.isDirectory(); const relativePath = path.join(dirPath, file); return { path: relativePath, name: file, size: stats.size, mtime: stats.mtime, isDirectory }; }); res.json(files); } catch (err) { console.error('获取文件列表失败:', err); res.status(500).json({ error: '获取文件列表失败: ' + err.message }); } }); /** * 删除文件 * DELETE /api/proxy/file?path=example.json */ router.delete('/proxy/file', (req, res) => { try { const filePath = req.query.path; if (!filePath) { return res.status(400).json({ error: '缺少文件路径参数' }); } // 处理相对路径和绝对路径 const fullPath = path.isAbsolute(filePath) ? filePath : path.join(FILES_DIR, filePath); // 确保路径安全(防止目录遍历攻击) const normalizedPath = path.normalize(fullPath); if (!normalizedPath.startsWith(FILES_DIR) && !path.isAbsolute(filePath)) { return res.status(403).json({ error: '访问被拒绝:路径超出允许范围' }); } if (!fs.existsSync(normalizedPath)) { return res.status(404).json({ error: '文件不存在' }); } // 删除文件或目录 fs.removeSync(normalizedPath); res.json({ success: true, message: '文件已删除' }); } catch (err) { console.error('删除文件失败:', err); res.status(500).json({ error: '删除文件失败: ' + err.message }); } }); /** * 测试 Mock 数据生成 * POST /api/proxy/test * body: { template: '{"name": "@cname", "age|18-60": 1}' } */ router.post('/proxy/test', (req, res) => { try { const { template } = req.body; if (!template) { return res.status(400).json({ error: '缺少 template 参数' }); } // 解析模板 let templateObj; try { templateObj = typeof template === 'string' ? JSON.parse(template) : template; } catch (e) { return res.status(400).json({ error: 'template 不是有效的 JSON: ' + e.message }); } // 生成 Mock 数据 const mockData = Mock.mock(templateObj); res.json({ template: templateObj, data: mockData }); } catch (err) { console.error('生成 Mock 数据失败:', err); res.status(500).json({ error: '生成 Mock 数据失败: ' + err.message }); } }); module.exports = router;