199 lines
6.5 KiB
C#
199 lines
6.5 KiB
C#
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.Data;
|
||
using Atomx.Data.Services;
|
||
using Atomx.Utils.Json.Converts;
|
||
using Blazored.LocalStorage;
|
||
using Mapster;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Components.Authorization;
|
||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||
using Microsoft.AspNetCore.HttpOverrides;
|
||
using Microsoft.AspNetCore.ResponseCompression;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Scalar.AspNetCore;
|
||
using Serilog;
|
||
using System;
|
||
using System.Linq;
|
||
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();
|
||
|
||
// 权限服务 & 授权处理器
|
||
builder.Services.AddScoped<IPermissionService, PermissionService>();
|
||
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
||
|
||
// AuthenticationStateProvider:Server 使用可重新验证的实现
|
||
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();
|
||
builder.Services.AddScoped<PersistentAuthenticationStateProvider>();
|
||
|
||
// 基本工具服务
|
||
builder.Services.AddScoped<IIdCreatorService, IdCreatorService>();
|
||
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
||
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
||
builder.Services.AddScoped<LocalizationFile, LocalizationFile>();
|
||
|
||
builder.Services.AddScoped<AuthHeaderHandler>();
|
||
|
||
// SignalR:启用服务端 Hub 支持(注意:JWT 的 OnMessageReceived 已在 AuthorizationExtension 中处理)
|
||
builder.Services.AddSignalR();
|
||
|
||
// HttpClient 与数据服务
|
||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||
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("cache");
|
||
builder.Services.AddStackExchangeRedisCache(options =>
|
||
{
|
||
options.Configuration = redisConnection;
|
||
options.InstanceName = builder.Configuration["RedisCache:InstanceName"];
|
||
});
|
||
|
||
// 响应压缩(保留)
|
||
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:生产环境要求显式配置允许来源以支持 Cookie(AllowCredentials)
|
||
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"));
|
||
|
||
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");
|
||
|
||
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>();
|
||
|
||
// SignalR endpoints(如项目包含 Hub,请在此处映射)
|
||
// 如果存在 Hub 类如 ChatHub、NotificationHub,请在此取消注释并映射
|
||
// app.MapHub<ChatHub>("/hubs/chat");
|
||
// app.MapHub<NotificationHub>("/hubs/notification");
|
||
|
||
app.MapControllers();
|
||
|
||
// Blazor 配置(Server + WASM render modes)
|
||
app.MapRazorComponents<App>()
|
||
.AddInteractiveServerRenderMode()
|
||
.AddInteractiveWebAssemblyRenderMode()
|
||
.AddAdditionalAssemblies(typeof(Atomx.Admin.Client._Imports).Assembly);
|
||
|
||
app.Run();
|