using Microsoft.Extensions.Options; using MyHomePage.Api.Common; using MyHomePage.Api.Infrastructure.Configuration; using MyHomePage.Api.Infrastructure.Database; using MyHomePage.Api.Services; using SqlSugar; var builder = WebApplication.CreateBuilder(args); // ===== 配置节点绑定 ===== builder.Services.Configure(builder.Configuration.GetSection(DatabaseOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(UploadOptions.SectionName)); builder.Services.Configure(builder.Configuration.GetSection(CorsOptions.SectionName)); // ===== 控制器 ===== builder.Services.AddControllers() .AddJsonOptions(o => { // 前端约定使用 camelCase o.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; o.JsonSerializerOptions.DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; }); // ===== Swagger / OpenAPI ===== builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new() { Title = "MyHomePage API", Version = "v1" }); // 注意:API 全部返回 ApiResponse,类型签名会进 swagger c.CustomOperationIds(apiDesc => { // Controller Action 有 action 键,minimal API(MapGet / MapPost 等)没有 // 用 TryGetValue 防御,避免 KeyNotFoundException 启动崩 var routeValues = apiDesc.ActionDescriptor.RouteValues; if (routeValues.TryGetValue("action", out var action) && !string.IsNullOrEmpty(action) && routeValues.TryGetValue("controller", out var controller) && !string.IsNullOrEmpty(controller)) { return $"{controller}_{action}"; } // minimal API 兜底:用路由模板做 operationId(去掉 / 和特殊字符) // 例:/health → health;/api/wallpaper/random → api_wallpaper_random return apiDesc.RelativePath? .Replace("/", "_", StringComparison.Ordinal) .Trim('_') ?? "UnknownEndpoint"; }); }); // ===== CORS ===== var corsOrigins = builder.Configuration.GetSection("Cors:Origins").Get() ?? Array.Empty(); builder.Services.AddCors(o => o.AddDefaultPolicy(p => { if (corsOrigins.Length > 0) p.WithOrigins(corsOrigins).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); else p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); })); // ===== SqlSugar 单例 ===== builder.Services.AddSingleton(); // ===== 服务依赖注入 ===== builder.Services.AddScoped(); builder.Services.AddScoped(sp => sp.GetRequiredService().Db); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ===== P31:favicon 自动抓取 ===== builder.Services.AddMemoryCache(); // IMemoryCache(24h 缓存已抓 favicon,Singleton) builder.Services.AddHttpClient(nameof(FaviconService), c => // 命名 HttpClient(IHttpClientFactory 管理生命周期) { c.Timeout = TimeSpan.FromSeconds(5); // 全局 5s 超时(个别 GET 还会二次限制) c.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"); }); builder.Services.AddScoped(); // 注入 IHttpClientFactory + IMemoryCache // ===== P34:360 在线壁纸代理 ===== builder.Services.AddHttpClient(nameof(WallpaperService), c => { c.Timeout = TimeSpan.FromSeconds(10); // 拉分类 / 200 张池子给 10s 充裕 c.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"); }); builder.Services.AddScoped(); // ===== 上传文件大小限制 ===== var maxUpload = builder.Configuration.GetValue("Upload:MaxSizeBytes") ?? 10L * 1024 * 1024; builder.Services.Configure(o => { o.MultipartBodyLengthLimit = maxUpload; }); builder.WebHost.ConfigureKestrel(o => o.Limits.MaxRequestBodySize = maxUpload); var app = builder.Build(); // ===== 启动时初始化数据库(CodeFirst + 种子) ===== using (var scope = app.Services.CreateScope()) { var initializer = scope.ServiceProvider.GetRequiredService(); await initializer.InitializeAsync(); } // ===== HTTP Pipeline ===== app.UseMiddleware(); // 静态文件:暴露 Upload 目录 var uploadPath = Path.IsPathRooted(app.Configuration["Upload:Path"] ?? "Uploads") ? app.Configuration["Upload:Path"]! : Path.Combine(app.Environment.ContentRootPath, app.Configuration["Upload:Path"] ?? "Uploads"); Directory.CreateDirectory(uploadPath); app.UseStaticFiles(new Microsoft.AspNetCore.Builder.StaticFileOptions { FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(uploadPath), RequestPath = app.Configuration["Upload:BaseUrl"] ?? "/uploads", ServeUnknownFileTypes = true }); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseCors(); app.UseAuthorization(); app.MapControllers(); // 根路径健康检查 app.MapGet("/", () => Results.Ok(new { name = "MyHomePage API", version = "1.0.0", status = "ok" })); app.MapGet("/health", () => Results.Ok(new { status = "ok", time = DateTimeOffset.UtcNow })); app.Run();