Files
F8WebAI/backend/AgriculturalPlatform.Api/Program.cs
T

139 lines
5.3 KiB
C#

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<AppDbContext>(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<JsonOptions>(o =>
{
o.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
o.SerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
});
// ---------- 业务服务 ----------
builder.Services.AddSingleton<JwtService>();
builder.Services.AddScoped<CurrentUserService>();
builder.Services.AddScoped<DataScopeService>();
builder.Services.AddScoped<NumberGenerator>();
builder.Services.AddHttpContextAccessor();
// ---------- 附件存储(本地为准,OSS 配置好后切换) ----------
builder.Services.AddSingleton<LocalFileStorage>();
builder.Services.AddSingleton<OssFileStorage>();
builder.Services.AddSingleton<IFileStorage>(sp =>
sp.GetRequiredService<IConfiguration>().GetValue("Storage:UseOss", false)
? sp.GetRequiredService<OssFileStorage>()
: sp.GetRequiredService<LocalFileStorage>());
// ---------- OCR 识别(阿里云市场身份证识别:Ocr:Provider=aliyun 时启用,否则占位提示) ----------
builder.Services.AddHttpClient("ocr", c => c.Timeout = TimeSpan.FromSeconds(30));
builder.Services.AddSingleton<IOcrService>(sp =>
{
var cfg = sp.GetRequiredService<IConfiguration>();
return string.Equals(cfg["Ocr:Provider"], "aliyun", StringComparison.OrdinalIgnoreCase)
? new AliyunOcrService(cfg, sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IFileStorage>())
: new DisabledOcrService(cfg);
});
// ---------- 手机传图上传会话 ----------
builder.Services.AddSingleton<MobileUploadSessionStore>();
// ---------- 天气服务(调用第三方天气接口) ----------
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<AppDbContext>();
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();