139 lines
5.3 KiB
C#
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();
|