Files
zszq-trs/YLErpWeb/Program.cs
hjhan 97029897c4 feat(swap): SwapCalcTrace 支持运行时按请求开关 + 响应头可见(无需重启)
- SwapCalcTrace 改为双模式:保留全局静态开关(单元测试用 IsEnabled/Dump),
  新增请求级 AsyncLocal 缓冲(EnableForRequest/DumpForRequest/ClearForRequest),
  由中间件按请求开关,不跨请求串扰。
- 新增 SwapCalcTraceMiddleware:开启方式三选一——URL ?swaptrace=1 /
  请求头 X-SwapCalc-Trace:1 / 标记文件 App_Data/swapcalctrace.on(无需重启);
  Response.OnStarting 时把逐日计息过程写入响应头 X-SwapCalc-Trace(超长落
  logs/swapcalctrace-YYYYMMDD.log 并在头给路径),同时写服务器日志。
  开发者在浏览器“网络”面板即可查看,零前端改码、零行为影响、性能可忽略。
- Program.cs 在 ExceptionMiddleware 后注册该中间件。
- 删除 CalcDailyCompoundInterestByEod 遗留垃圾日志 LogFactory.GetLogger("test").Error("lksafhasdhfjas")。
- CalcDailySimpleInterest 两处追踪守卫改为 IsEnabled||IsRequestEnabled。
- 既有 PrepaidPrincipalCloseTraceTest / ClosingChainTraceTest 2/2 仍全绿。

回答用户两点:① dynomicPrincipal 公式字段含义;② 原 Trace 需重启才能开,
现改为前端 otcdebug 式(?swaptrace=1) 即可在响应头看到逐步计息过程。
2026-08-08 15:57:00 +08:00

246 lines
9.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.WebEncoders;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using NLog;
using NLog.Web;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using YieldChain.Commons;
using YLErp.Abstract;
using YLErp.Serialization;
using YLErp.Services.CashRecordModule;
using YLErp.Web.App;
using YLErp.Web.App.KafkaTask;
using YLErp.Web.Hubs;
using YLErp.Web.Middleware;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
JsonConvert.DefaultSettings = JsonHelper.GetDefaultSettings;
var logger = LogManager.Setup().GetCurrentClassLogger();
logger.Debug("init main");
try
{
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddHttpClient();
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.Converters.Add(new AntiXssConverter());
options.JsonSerializerOptions.Converters.Add(CustomDoubleConvert.Default);
options.JsonSerializerOptions.Converters.Add(new CustomDateTimeConverter());
}).AddRazorRuntimeCompilation();
//视图渲染绑定中文乱码问题
builder.Services.Configure<WebEncoderOptions>(options => options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));
builder.Services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = 1000000;
options.ValueLengthLimit = 1024 * 1024 * 100; // 100MB max len form data
options.MultipartBodyLengthLimit = int.MaxValue; //不限制文件上传大小
});
builder.Services.Configure<MvcOptions>(options => options.MaxModelBindingCollectionSize = 1000000);
builder.Services.Configure<KafkaConfig>(builder.Configuration.GetSection("KafkaConfig"));
builder.Services.Configure<ExternalCashFlowOptions>(builder.Configuration.GetSection("ExternalCashFlow"));
builder.Services.AddScoped<IViewRenderService, ViewRenderService>();
builder.Services.AddSingleton<IKafkaProduce, KafkaProduceHelper>();
builder.Services.AddTransient<IExternalCashFlowService, ExternalCashFlowService>();
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
});
builder.Services.AddAuthentication(AuthHelper.CookieAuthType)
.AddCookie(AuthHelper.CookieAuthType, options =>
{
options.LoginPath = "/Account/Login/";
options.AccessDeniedPath = "/Account/Login/";
options.ExpireTimeSpan = TimeSpan.FromSeconds(AuthHelper.ExpireInSeconds);
options.SlidingExpiration = true;
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
{
var accept = context.Request.Headers["Accept"].ToString();
var xrw = context.Request.Headers["X-Requested-With"].ToString();
var isAjax = xrw == "XMLHttpRequest" || !accept.Contains("text/html");
logger.Info($"[CookieAuth] OnRedirectToLogin Path={context.Request.Path} Accept={accept} X-Requested-With={xrw} IsAjax={isAjax}");
if (isAjax)
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
},
OnRedirectToAccessDenied = context =>
{
var accept = context.Request.Headers["Accept"].ToString();
var xrw = context.Request.Headers["X-Requested-With"].ToString();
var isAjax = xrw == "XMLHttpRequest" || !accept.Contains("text/html");
logger.Info($"[CookieAuth] OnRedirectToAccessDenied Path={context.Request.Path} Accept={accept} X-Requested-With={xrw} IsAjax={isAjax}");
if (isAjax)
{
context.Response.StatusCode = 403;
return Task.CompletedTask;
}
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
}
};
})
.AddJwtBearer(AuthHelper.JwtAuthType, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(AuthHelper.ExpireInSeconds),
ValidateAudience = false,
ValidateIssuer = false, //是否验证Issuer
ValidateIssuerSigningKey = true, //是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AuthHelper.JwtSecretKey))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(AuthHelper.CookieAuthType, policy => policy.RequireAssertion(context => ErpAuthorizeHandler.Default.Handle(context)));
options.AddPolicy(AuthHelper.JwtAuthType, policy => policy.RequireAssertion(context => ErpAuthorizeHandler.Default.Handle(context)));
});
//NLog: Setup NLog for Dependency injection
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(LogLevel.Trace);
builder.Host.UseNLog();
// Save IServerCollection
YLServiceLocator.SetServiceCollection(builder.Services);
Server.Initialize(builder.Environment, builder.Configuration);
Startup.Start(builder.Configuration);
builder.Services.AddHostedService<ClientBalanceTask>();
builder.Services.AddHostedService<ClientReskCheckKafkaTask>();
builder.Services.AddHostedService<HostedTaskService>();
builder.Services.AddHostedService<ClientBalanceMonitorTotalKafkaTask>();
builder.Services.AddHostedService<ClientBalanceMonitorSingleKafkaTask>();
builder.Services.AddHostedService<TrsExchangeTradeConsumerKafkaTask>();
builder.Services.AddHostedService<HedgingOrderCancelConsumerKafkaTask>();
builder.Services.AddHostedService<HolidayConsumerKafkaTask>();
builder.Services.AddHostedService<TrsSwapFlowConsumerKafkaTask>();
builder.Services.AddCaptcha(builder.Configuration, option =>
{
option.ImageOption.Width = 256; // 验证码宽度
option.ImageOption.Height = 34; // 验证码高度
option.ExpirySeconds = 60; // 验证码过期时间
option.IgnoreCase = true; // 比较时是否忽略大小写
option.ImageOption.BubbleCount = 0;
option.ImageOption.InterferenceLineCount = 3; // 干扰线数量
});
builder.Services.AddSignalR();
//============================================
var app = builder.Build();
// Save IServerProvider
YLServiceLocator.SetServiceProvider(app.Services);
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
}
app.UseMiddleware<ExceptionMiddleware>();
// 计息计算过程追踪(?swaptrace=1 / X-SwapCalc-Trace:1 开启,详见 SwapCalcTraceMiddleware
app.UseMiddleware<SwapCalcTraceMiddleware>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// Linux 路径区分大小写,需要对静态文件做处理
app.UseMiddleware<StaticUrlMiddleware>();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
// Token续期中间件
app.UseMiddleware<RefreshTokenMiddleware>();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<SwapFlowCombookingHub>("/swapflow/combookinghub"); // 映射Hub路径
endpoints.MapHub<SwapFlowResetHub>("/swapflow/resethub"); // 映射Hub路径
endpoints.MapHub<SwapConfirmSendEmailHub>("/tradeconfirm/sendemailhub"); // 映射Hub路径
endpoints.MapHub<TradeConfirmSendOAHub>("/tradeconfirm/sendoahub"); // 映射OA Hub路径
});
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".pdf"] = "application/pdf";
provider.Mappings[".doc"] = "application/msword";
provider.Mappings[".docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
provider.Mappings[".xls"] = "application/vnd.ms-excel";
provider.Mappings[".xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Server.MapPath("App_Docs")),
RequestPath = "/App_Docs",
ContentTypeProvider = provider
});
app.UseResponseCompression();
app.MapAreaControllerRoute(
name: "MyAreaAdmin",
areaName: "Admin",
pattern: "Admin/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "mapi",
pattern: "m/api/{*path}");
app.MapControllerRoute(
name: "v3",
pattern: "v3/{*path}",
new { controller = "V3", action = "Index" });
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
catch (Exception ex)
{
// NLog: catch setup errors
logger.Error(ex, "Stopped program because of exception");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
LogManager.Shutdown();
}