using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using System; using System.IO; using System.Threading.Tasks; using YLErp.Helpers; using YLErp.Modules.SwapModule; namespace YLErp.Web.Middleware { /// /// 计息计算过程追踪中间件(运行时排障用,无需重启、无需改前端)。 /// /// 开启方式(三选一): /// 1) 请求 URL 追加 ?swaptrace=1 /// 2) 请求头带 X-SwapCalc-Trace: 1 /// 3) 站点根目录 App_Data/swapcalctrace.on 标记文件存在(对所有请求开启,谨慎使用) /// /// 开启后,SwapCalcTrace 会在计息函数中逐日记录过程。本中间件在响应发出前把追踪文本写入: /// - 响应头 X-SwapCalc-Trace(开发者在浏览器"网络"面板即可看到,超长则写入日志并在头部给文件路径) /// - 服务器日志 logs/swapcalctrace-YYYYMMDD.log(完整文本,便于 tail/检索) /// /// 性能影响可忽略(仅被显式开启的少数请求触发,且每次只是拼字符串)。 /// public class SwapCalcTraceMiddleware { private readonly RequestDelegate _next; private readonly IWebHostEnvironment _env; public SwapCalcTraceMiddleware(RequestDelegate next, IWebHostEnvironment env) { _next = next; _env = env; } public async Task InvokeAsync(HttpContext context) { bool on = context.Request.Query["swaptrace"] == "1" || context.Request.Headers["X-SwapCalc-Trace"] == "1"; if (!on) on = MarkerFileExists(); if (on) SwapCalcTrace.EnableForRequest(true); try { if (on) { // OnStarting 在响应体写出前触发,确保整个请求(含计息)都已被追踪 context.Response.OnStarting(() => { FlushTrace(context); return Task.CompletedTask; }); } await _next(context); } finally { if (on) SwapCalcTrace.ClearForRequest(); } } private void FlushTrace(HttpContext context) { var trace = SwapCalcTrace.DumpForRequest(); if (string.IsNullOrEmpty(trace)) return; var logger = LogFactory.GetLogger("SwapCalcTrace"); logger.Info($"[SwapCalcTrace] {context.Request.Path}\n{trace}"); var headerVal = trace; if (trace.Length > 4000) { try { var dir = Path.Combine(_env.ContentRootPath, "logs"); Directory.CreateDirectory(dir); var file = Path.Combine(dir, $"swapcalctrace-{DateTime.Now:yyyyMMdd}.log"); File.AppendAllText(file, $"=== {DateTime.Now:yyyy-MM-dd HH:mm:ss} {context.Request.Path} ===\n{trace}\n\n"); headerVal = $"(trace truncated, full in {file})\n" + trace.Substring(0, 4000); } catch { headerVal = trace.Substring(0, 4000); } } try { context.Response.Headers["X-SwapCalc-Trace"] = headerVal; } catch { } } private bool MarkerFileExists() { try { return File.Exists(Path.Combine(_env.ContentRootPath, "App_Data", "swapcalctrace.on")); } catch { return false; } } } }