UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

156 lines (144 loc) 4.26 kB
// 导出中间件主方法 module.exports = { /** * 初始化 * @param {object} adapter 适配器集合 */ init(adapter) { // 获取web服务 var web = adapter.web; // 注册中间件 web.use(async (ctx, next) => { if (ctx.path === '/favicon.ico') { await next(); } else { ctx.ip = this._getClientIp(ctx); ctx.start_time = Date.now(); let log = { method: ctx.method, path: ctx.path, ip: ctx.ip || this._getClientIp(ctx), request: this._format(ctx), user_agent: this._getUserAgent(ctx) }; try { await next(); // 请求后置处理 log.duration = Date.now() - ctx.start_time; log.response = this._sanitizeBody(ctx.body) || {}; log.status = ctx.status; // 请求前置处理 this._record(log); } catch (err) { // 记录错误日志 log.duration = Date.now() - ctx.start_time; log.status = 500; log.response = { error: { message: err.message, stack: err.stack } }; this._record(log); // 重新抛出错误,由web_error中间件统一处理 throw err; } } }); }, _getClientIp(ctx) { return ctx.headers['x-forwarded-for']?.split(',')[0]?.trim() || ctx.headers['x-real-ip'] || 'unknown'; }, /** * 获取用户代理信息 * @private * @param {object} ctx Koa上下文 * @returns {string} 用户代理信息 */ _getUserAgent(ctx) { let ua = ctx.headers['user-agent'] || ''; // 简化的 UA 解析,只提取浏览器和 OS if (ua.includes('Chrome')) return 'Chrome'; if (ua.includes('Firefox')) return 'Firefox'; if (ua.includes('Safari')) return 'Safari'; if (ua.includes('Postman')) return 'Postman'; if (ua.includes('curl')) return 'curl'; return ua.length > 20 ? ua.substring(0, 20) + '...' : ua; }, /** * 格式化请求信息 * @private * @param {object} ctx Koa上下文 * @returns {object} 格式化后的请求数据 */ _format(ctx) { // const headers = this._sanitizeHeaders(ctx.headers); let body = ctx.request.body ? this._sanitizeBody(ctx.request.body) : undefined; return { query: ctx.query, body: body }; }, /** * 清理敏感头信息 * @private * @param {object} headers 请求头 * @returns {object} 清理后的请求头 */ _sanitizeHeaders(headers) { const SENS_HEADERS = ['authorization', 'cookie', 'x-api-key']; const SAN_VAL = {}; for (var k in headers) { if (SENS_HEADERS.includes(k.toLowerCase())) { SAN_VAL[k] = '[REDACTED]'; } else { SAN_VAL[k] = headers[k]; } } return SAN_VAL; }, /** * 清理敏感请求体信息 * @private * @param {object} body 请求体 * @returns {object} 清理后的请求体 */ _sanitizeBody(body) { const SENS_KEYS = ['password', 'token', 'secret', 'key', 'credit_card']; let by = {}; if (typeof(body) === 'object') { for (var k in body) { if (SENS_KEYS.some(field => k.toLowerCase().includes(field))) { by[k] = '[REDACTED]'; } else { by[k] = body[k]; } } } return by; }, /** * 记录请求日志 * @private * @param {object} log 日志对象 */ _record(log) { var status = log.status; this.log('http', `[${log.ip}] [${log.method}] ${log.path} ${status} ${log.duration}ms`, log.request, log.response); // 时间 time '2022-11-11 12:00:00' // 信息级别 level 'warn' // 来源IP ip '127.0.0.1' // 浏览器 user_agent 'Chrome' X // 查询条件 query '{'id':1}' X // 提交内容 body '{'name':'test'}' X // 错误信息 error 'Error: test' X // 请求方法 method 'GET' // 请求路径 path '/test' // 响应时长 duration '38ms' // 响应状态 status 200 // 用户ID user_id 1 } };