Files
AI-Health/backend/src/Health.WebApi/Program.cs
MingNian 287eab80a9 fix: 全面修复 - 实时通讯、报告AI流程、健康概览、用药运动提醒
- SignalR Hub消息持久化+防重复+跨端广播
- 报告VLM+LLM真实AI流程(验证→提取→解读)
- 医生审核页重构(严重程度/建议模板/综合评语)
- 健康概览五合一趋势图(fl_chart+指标切换)
- 用药提醒时区修复+跨午夜+过期提醒
- 运动打卡DayOfWeek映射修复+计划覆盖查询
- 体重与其他四指标同级(Abnormal检查/确认卡牌)
- AI Agent支持多种类多时段批量录入
- 删除硬编码假数据(张三/假医生/假AI解读)
- 随访/报告审核数据链路全部打通
2026-06-07 23:04:23 +08:00

141 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text;
using Health.Infrastructure.AI;
using Health.Infrastructure.Data;
using Health.Infrastructure.Services;
using Health.WebApi.BackgroundServices;
using Health.WebApi.Endpoints;
using Health.WebApi.Middleware;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
// 加载 .env 文件(开发环境)
var envPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", ".env");
if (File.Exists(envPath))
{
foreach (var line in File.ReadAllLines(envPath))
{
var trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith('#')) continue;
var eqIdx = trimmed.IndexOf('=');
if (eqIdx <= 0) continue;
var key = trimmed[..eqIdx].Trim();
var value = trimmed[(eqIdx + 1)..].Trim();
Environment.SetEnvironmentVariable(key, value);
}
}
var builder = WebApplication.CreateBuilder(args);
// ---- JSON 配置枚举字符串、camelCase----
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
options.SerializerOptions.PropertyNameCaseInsensitive = true;
});
// ---- 数据库 ----
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default") ?? builder.Configuration["DB_CONNECTION"] ?? "Host=localhost;Database=health_manager;Username=postgres;Password=postgres"));
// ---- JWT 认证 ----
var jwtSecret = builder.Configuration["JWT_SECRET"];
if (string.IsNullOrEmpty(jwtSecret) && !builder.Environment.IsDevelopment())
throw new InvalidOperationException("JWT_SECRET 环境变量未配置,生产环境必须设置");
jwtSecret ??= "dev-secret-key-change-in-production-min-32-chars!!";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JWT_ISSUER"] ?? "health-manager",
ValidAudience = builder.Configuration["JWT_AUDIENCE"] ?? "health-manager-app",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)),
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
// ---- 业务服务 ----
builder.Services.AddSingleton<JwtProvider>();
builder.Services.AddSingleton<SmsService>();
builder.Services.AddSingleton<PromptManager>();
// ---- AI 客户端(使用 IHttpClientFactory 区分 LLM 和 VLM----
builder.Services.AddHttpClient<DeepSeekClient>(client =>
{
client.BaseAddress = new Uri((builder.Configuration["DEEPSEEK_BASE_URL"] ?? "https://api.deepseek.com/v1").TrimEnd('/') + "/");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", builder.Configuration["DEEPSEEK_API_KEY"] ?? "");
client.Timeout = TimeSpan.FromSeconds(60);
});
builder.Services.AddHttpClient<VisionClient>(client =>
{
client.BaseAddress = new Uri((builder.Configuration["VLM_BASE_URL"] ?? "https://dashscope.aliyuncs.com/compatible-mode/v1").TrimEnd('/') + "/");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", builder.Configuration["VLM_API_KEY"] ?? "");
client.Timeout = TimeSpan.FromSeconds(120);
});
// ---- 后台服务 ----
builder.Services.AddHostedService<MedicationReminderService>();
builder.Services.AddHostedService<CleanupService>();
// ---- OpenAPI ----
builder.Services.AddOpenApi();
// ---- SignalR ----
builder.Services.AddSignalR();
// ---- CORSSignalR 需要明确 origins + AllowCredentials----
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.SetIsOriginAllowed(_ => true).AllowAnyMethod().AllowAnyHeader().AllowCredentials();
});
});
var app = builder.Build();
// ---- 中间件管道ExceptionMiddleware 放最前面)----
app.UseMiddleware<ExceptionMiddleware>();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
if (app.Environment.IsDevelopment())
app.MapOpenApi();
// ---- 初始化数据库(开发环境:每次重建)----
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.EnsureCreatedAsync();
await DataSeeder.SeedAsync(db);
await DevDataSeeder.SeedIfEnabled(db, app.Configuration);
}
// ---- 注册 API 端点 ----
app.MapAuthEndpoints();
app.MapHealthEndpoints();
app.MapDietEndpoints();
app.MapMedicationEndpoints();
app.MapReportEndpoints();
app.MapConsultationEndpoints();
app.MapExerciseEndpoints();
app.MapUserEndpoints();
app.MapAiChatEndpoints();
app.MapFileEndpoints();
app.MapCalendarEndpoints();
app.MapDoctorEndpoints();
app.MapFollowUpEndpoints();
// SignalR Hub
app.MapHub<Health.WebApi.Hubs.ConsultationHub>("/hubs/consultation");
app.Run();