mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
156 lines (144 loc) • 4.26 kB
JavaScript
// 导出中间件主方法
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
}
};