Files
Atomx/Atomx.Admin/Atomx.Admin/Program.cs
2025-12-25 01:08:36 +08:00

265 lines
8.4 KiB
C#
Raw 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 Atomx.Admin.Client.Services;
using Atomx.Admin.Client.Utils;
using Atomx.Admin.Components;
using Atomx.Admin.Extensions;
using Atomx.Admin.Middlewares;
using Atomx.Admin.Models;
using Atomx.Admin.Services;
using Atomx.Admin.Utils;
using Atomx.Common.Models;
using Atomx.Core.Jos;
using Atomx.Data;
using Atomx.Data.Services;
using Atomx.Utils.Json.Converts;
using Blazored.LocalStorage;
using FluentValidation;
using Hangfire;
using Hangfire.PostgreSql;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Scalar.AspNetCore;
using Serilog;
using System.Globalization;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
var builder = WebApplication.CreateBuilder(args);
// Serilog 配置(保持原样)
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "Atomx.Admin")
.Enrich.WithProperty("Environment", builder.Environment.EnvironmentName)
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
// 基本服务注册
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(new LongJsonConverter());
});
builder.Services.AddRouting(p => p.LowercaseUrls = true);
builder.Services.AddMapster();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();
// HttpClient 与数据服务
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
// 注入本地化提供程序与服务
builder.Services.AddScoped<ILocalizationProvider, LocalizationProvider>();
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(JsonStringLocalizer<>));
// 添加本地化服务
builder.Services.AddLocalization();
// 权限服务 & 授权处理器
builder.Services.AddScoped<IPermissionService, PermissionService>();
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
// AuthenticationStateProviderServer 使用可重新验证的实现
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();
builder.Services.AddScoped<PersistentAuthenticationStateProvider>();
// 基本工具服务
builder.Services.AddScoped<IIdCreatorService, IdCreatorService>();
builder.Services.AddScoped<IIdentityService, IdentityService>();
builder.Services.AddScoped<AuthHeaderHandler>();
// SignalR启用服务端 Hub 支持注意JWT 的 OnMessageReceived 已在 AuthorizationExtension 中处理)
builder.Services.AddSignalR();
builder.Services.AddDataService();
builder.Services.AddAuthorize(builder.Configuration, builder.Environment); // 引入我们配置好的认证/授权
// EF Core DbContext
var connection = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<DataContext>(options => options.UseNpgsql(connection, p => p.MigrationsHistoryTable("__DbMigrationsHistory")));
// Redis 缓存
var redisConnection = builder.Configuration.GetConnectionString("RedisConnection");
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnection;
options.InstanceName = builder.Configuration["RedisCache:InstanceName"];
});
// Hangfire 配置
var hangfireConnection = builder.Configuration.GetConnectionString("HangfireConnection")
?? throw new InvalidOperationException("Connection string 'HangfireConnection' not found.");
// 添加hangfire服务并配置PostgreSQL存储
builder.Services.AddHangfire(config =>
{
config.UsePostgreSqlStorage(
options =>
{
options.UseNpgsqlConnection(hangfireConnection);
},
new PostgreSqlStorageOptions
{
// Additional PostgreSQL storage options
PrepareSchemaIfNecessary = true,
QueuePollInterval = TimeSpan.FromSeconds(10) // 设置队列轮询间隔
}).UseFilter(new AutomaticRetryAttribute { Attempts = 3 });
});
// 启用 Hangfire 仪表盘和服务器
builder.Services.AddHangfireServer();
// 响应压缩(保留)
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes
.Where(m => !string.Equals(m, "text/html", StringComparison.OrdinalIgnoreCase))
.ToArray();
});
// CORS生产环境要求显式配置允许来源以支持 CookieAllowCredentials
var corsOrigins = builder.Configuration["Cors:AllowedOrigins"];
if (!string.IsNullOrEmpty(corsOrigins))
{
var origins = corsOrigins.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
builder.Services.AddCors(options =>
{
options.AddPolicy("DefaultCors", policy =>
{
policy.WithOrigins(origins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
}
else
{
// 开发时快速允许任意 origin不推荐生产
builder.Services.AddCors(options =>
{
options.AddPolicy("DefaultCors", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
// 注意AllowAnyOrigin 与 AllowCredentials 不能同时使用
});
});
}
builder.Services.AddOpenApi();
builder.Services.AddAntDesign();
builder.Services.Configure<MonitoringOptions>(builder.Configuration.GetSection("Monitoring"));
// 注册 FluentValidation 验证器
builder.Services.AddValidatorsFromAssembly(typeof(Atomx.Admin.Client.Validators.LoginModelValidator).Assembly);
// Register IBackgroundJobService and its implementation
builder.Services.AddScoped<IBackgroundJobService, BackgroundJobService>();
var app = builder.Build();
app.AddDataMigrate();
// Forwarded headers反向代理
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
app.MapScalarApiReference();
app.MapOpenApi();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
app.UseResponseCompression();
// 使用命名的 CORS 策略
app.UseCors("DefaultCors");
// 注册 RequestCultureMiddleware使外部带短码的请求先设置 Culture 并剥离前缀
app.UseMiddleware<RequestCultureMiddleware>();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={31536000}");
}
});
// 中间件顺序:认证 -> 授权
app.UseAuthentication();
app.UseAuthorization();
// Antiforgery & 其他中间件
app.UseAntiforgery();
app.MapStaticAssets();
app.UseMiddleware<MonitoringMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
// 配置本地化中间件
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("zh-Hans"),
SupportedCultures = new[]
{
new CultureInfo("zh-Hans"),
new CultureInfo("en-US")
},
SupportedUICultures = new[]
{
new CultureInfo("zh-Hans"),
new CultureInfo("en-US")
}
});
// SignalR endpoints如项目包含 Hub请在此处映射
// 如果存在 Hub 类如 ChatHub、NotificationHub请在此取消注释并映射
// app.MapHub<ChatHub>("/hubs/chat");
// app.MapHub<NotificationHub>("/hubs/notification");
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
});
app.MapControllers();
// Blazor 配置Server + WASM render modes
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Atomx.Admin.Client._Imports).Assembly);
app.Run();