using System.Text; using System.Text.Json.Serialization; using AgriculturalPlatform.Api.Data; using AgriculturalPlatform.Api.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http.Json; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); // ---------- 注册 GBK 等编码(天气 IP 定位接口使用) ---------- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // ---------- 在线 MySQL 数据库 ---------- var connectionString = builder.Configuration.GetConnectionString("Default") ?? throw new InvalidOperationException("未配置数据库连接字符串 ConnectionStrings:Default"); var serverVersion = new MySqlServerVersion(new Version( builder.Configuration.GetValue("MySql:Major", 8), builder.Configuration.GetValue("MySql:Minor", 0), builder.Configuration.GetValue("MySql:Build", 36))); builder.Services.AddDbContext(options => options.UseMySql(connectionString, serverVersion, mysql => { mysql.EnableRetryOnFailure(3); mysql.CommandTimeout(30); })); // ---------- JWT 认证 ---------- var jwtKey = builder.Configuration["Jwt:Key"] ?? "AgriculturalPlatform-Default-Jwt-Secret-Key-2026!"; builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"] ?? "AgriculturalPlatform", ValidAudience = builder.Configuration["Jwt:Audience"] ?? "AgriculturalPlatform.Client", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) }; options.Events = new JwtBearerEvents { OnMessageReceived = ctx => { // 兼容前端直接携带 token 的查询参数方式 if (string.IsNullOrEmpty(ctx.Token) && ctx.Request.Query.TryGetValue("access_token", out var token)) { ctx.Token = token; } return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); // ---------- JSON:枚举序列化为字符串、中文不被转义 ---------- builder.Services.Configure(o => { o.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); o.SerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; }); // ---------- 业务服务 ---------- builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpContextAccessor(); // ---------- 附件存储(本地为准,OSS 配置好后切换) ---------- builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService().GetValue("Storage:UseOss", false) ? sp.GetRequiredService() : sp.GetRequiredService()); // ---------- OCR 识别(阿里云市场身份证识别:Ocr:Provider=aliyun 时启用,否则占位提示) ---------- builder.Services.AddHttpClient("ocr", c => c.Timeout = TimeSpan.FromSeconds(30)); builder.Services.AddSingleton(sp => { var cfg = sp.GetRequiredService(); return string.Equals(cfg["Ocr:Provider"], "aliyun", StringComparison.OrdinalIgnoreCase) ? new AliyunOcrService(cfg, sp.GetRequiredService(), sp.GetRequiredService()) : new DisabledOcrService(cfg); }); // ---------- 手机传图上传会话 ---------- builder.Services.AddSingleton(); // ---------- 天气服务(调用第三方天气接口) ---------- builder.Services.AddHttpClient("weather"); // ---------- 前端(Vue 开发服务器)跨域 ---------- var frontendUrl = builder.Configuration["FrontendUrl"] ?? "http://localhost:5173"; builder.Services.AddCors(o => o.AddPolicy("frontend", p => p.WithOrigins(frontendUrl.Split(';')).AllowAnyHeader().AllowAnyMethod().AllowCredentials())); builder.Services.AddControllers(); builder.Services.AddOpenApi(); var app = builder.Build(); // ---------- 初始化数据库(自动建库建表 + 种子数据) ---------- try { using var scope = app.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); await SchemaMigrator.EnsureColumnsAsync(db); await SchemaMigrator.EnsureSystemTablesAsync(db); await SchemaMigrator.EnsureRegionsTableAsync(db); DbSeeder.Seed(db); app.Logger.LogInformation("数据库初始化完成"); } catch (Exception ex) { app.Logger.LogError(ex, "数据库初始化失败,请检查 ConnectionStrings:Default 配置"); } app.UseCors("frontend"); app.UseAuthentication(); app.UseAuthorization(); // 附件与手机上传页面(wwwroot) app.UseDefaultFiles(); app.UseStaticFiles(); app.MapOpenApi(); app.MapControllers(); app.Run();