基于NodeJS Express+Postgres+Sequelize的API请求响应存储与调试需求
实现基于Express+Sequelize的API请求调试沙箱(迷你Wireshark)
我来给你一步步实现这个迷你调试沙箱,正好适配你现有的Express+Sequelize+Postgres栈,完全满足请求响应存储、网页表格展示和JSON导出的需求:
1. 创建请求日志的Sequelize模型
首先定义一个RequestLog模型,用来存储每一条请求的完整信息。在你的Sequelize模型目录下新建requestLog.js:
// models/requestLog.js module.exports = (sequelize, DataTypes) => { const RequestLog = sequelize.define('RequestLog', { method: { type: DataTypes.STRING(10), // GET/POST/PUT等,长度足够 allowNull: false }, path: { type: DataTypes.STRING(255), allowNull: false }, requestBody: { type: DataTypes.TEXT, // 支持大体积请求体 allowNull: true }, responseBody: { type: DataTypes.TEXT, // 支持大体积响应体 allowNull: true }, statusCode: { type: DataTypes.INTEGER, allowNull: false } }, { timestamps: true, // 自动生成createdAt和updatedAt字段 tableName: 'request_logs' }); return RequestLog; };
然后在Sequelize初始化文件中导入这个模型,执行sequelize.sync()或者用迁移脚本完成数据库表同步。
2. 编写Express中间件捕获请求与响应
接下来写一个全局中间件,用来捕获所有经过API的请求和响应。注意要把这个中间件放在express.json()和express.urlencoded()之后,确保能正确解析请求体:
// middleware/requestLogger.js const { RequestLog } = require('../models'); const requestLogger = async (req, res, next) => { // 捕获请求基本信息 const requestInfo = { method: req.method, path: req.path, requestBody: JSON.stringify(req.body) // 转成字符串方便存储 }; // 重写res.json和ressend方法,捕获响应内容 const originalJson = res.json; const originalSend = res.send; let responseBody = ''; res.json = function(data) { responseBody = JSON.stringify(data); return originalJson.call(this, data); }; res.send = function(data) { if (typeof data === 'object') { responseBody = JSON.stringify(data); } else { responseBody = data.toString(); } return originalSend.call(this, data); }; // 响应结束后异步存储日志到数据库 res.on('finish', async () => { try { await RequestLog.create({ ...requestInfo, responseBody, statusCode: res.statusCode }); } catch (err) { // 日志存储失败不影响主业务流程 console.error('Failed to save request log:', err); } }); next(); }; module.exports = requestLogger;
在Express主文件中注册这个中间件:
// app.js const express = require('express'); const requestLogger = require('./middleware/requestLogger'); const app = express(); // 先解析请求体 app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 注册日志中间件(放在所有路由之前) app.use(requestLogger); // 你的业务路由 app.use('/api', require('./routes/api')); // ...其他服务配置
3. 后端接口:日志查询与JSON导出
写两个接口,分别用来分页获取日志列表和导出所有日志为JSON文件:
// routes/debug.js const express = require('express'); const { RequestLog } = require('../models'); const router = express.Router(); // 分页获取日志列表 router.get('/logs', async (req, res) => { const { page = 1, limit = 20 } = req.query; const offset = (page - 1) * limit; try { const { count, rows } = await RequestLog.findAndCountAll({ order: [['createdAt', 'DESC']], // 最新日志优先 limit: parseInt(limit), offset: parseInt(offset) }); res.json({ total: count, page: parseInt(page), limit: parseInt(limit), logs: rows }); } catch (err) { res.status(500).json({ error: 'Failed to fetch logs' }); } }); // 导出所有日志为JSON router.get('/logs/export', async (req, res) => { try { const logs = await RequestLog.findAll({ order: [['createdAt', 'DESC']] }); // 设置响应头触发下载 res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', 'attachment; filename=api-logs.json'); // 格式化JSON后发送 res.send(JSON.stringify(logs, null, 2)); } catch (err) { res.status(500).json({ error: 'Failed to export logs' }); } }); module.exports = router;
在主文件中注册调试路由:
// app.js app.use('/debug', require('./routes/debug')); // 静态文件服务(用于加载前端页面) app.use(express.static('public'));
4. 前端日志表格页面
新建public/debug.html,实现日志表格展示和导出功能:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>API Debug Sandbox</title> <style> table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .log-details { display: none; margin-top: 10px; padding: 10px; background-color: #f9f9f9; border-radius: 4px; } .expand-btn { cursor: pointer; color: #007bff; } .export-btn { padding: 10px 20px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; margin: 20px 0; } pre { white-space: pre-wrap; word-wrap: break-word; } </style> </head> <body> <h1>API Request/Response Logs</h1> <button class="export-btn" onclick="exportLogs()">Export as JSON</button> <table id="logs-table"> <thead> <tr> <th>Timestamp</th> <th>Method</th> <th>Path</th> <th>Status Code</th> <th>Actions</th> </tr> </thead> <tbody id="logs-body"></tbody> </table> <script> // 获取并渲染日志列表 async function fetchLogs() { try { const response = await fetch('/debug/logs?limit=100'); const data = await response.json(); const tbody = document.getElementById('logs-body'); tbody.innerHTML = ''; data.logs.forEach(log => { const row = document.createElement('tr'); row.innerHTML = ` <td>${new Date(log.createdAt).toLocaleString()}</td> <td>${log.method}</td> <td>${log.path}</td> <td>${log.statusCode}</td> <td><span class="expand-btn" onclick="toggleDetails(${log.id})">View Details</span></td> `; // 详情行 const detailsRow = document.createElement('tr'); detailsRow.id = `details-${log.id}`; detailsRow.innerHTML = ` <td colspan="5"> <div class="log-details"> <h3>Request Body:</h3> <pre>${formatJson(log.requestBody)}</pre> <h3>Response Body:</h3> <pre>${formatJson(log.responseBody)}</pre> </div> </td> `; tbody.appendChild(row); tbody.appendChild(detailsRow); }); } catch (err) { alert('Failed to fetch logs'); console.error(err); } } // 切换详情显示/隐藏 function toggleDetails(logId) { const details = document.getElementById(`details-${logId}`).querySelector('.log-details'); details.style.display = details.style.display === 'block' ? 'none' : 'block'; } // 格式化JSON字符串 function formatJson(jsonStr) { try { return JSON.stringify(JSON.parse(jsonStr), null, 2); } catch (err) { return jsonStr; // 非JSON格式直接显示原字符串 } } // 导出日志 function exportLogs() { window.open('/debug/logs/export', '_blank'); } // 页面加载时初始化,每5秒自动刷新 window.onload = fetchLogs; setInterval(fetchLogs, 5000); </script> </body> </html>
启动服务后访问http://localhost:3000/debug.html,就能看到实时更新的请求日志表格,点击"View Details"可展开查看请求/响应体,点击导出按钮即可下载JSON格式的日志文件。
5. 关键优化与注意事项
- 敏感数据过滤:如果请求/响应包含密码、token等敏感信息,需在中间件中提前过滤或加密,比如将
requestBody中的password字段替换为***。 - 日志清理:添加定时任务(如
node-schedule)定期删除N天前的日志,避免数据库表膨胀。 - 性能优化:给
method、path、createdAt字段添加数据库索引,提升查询速度;日志量极大时建议实现前端滚动加载。 - 错误隔离:日志存储逻辑需用try-catch包裹,确保日志存储失败不会影响主业务流程。
内容的提问来源于stack exchange,提问作者code-8
相关产品推荐
相关产品推荐

