chore: 全面规范化代码,遵循 CLAUDE.md 编码规范
- C# 文件命名改为 snake_case(28 个文件重命名) - C# 类转换为主构造函数(8 个类) - 空 catch 添加异常类型(2 处) - 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi) - Flutter 移除 go_router,改用 Riverpod 路由栈 - Flutter 移除 flutter_secure_storage,改用 sqflite 持久化 - 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘) - 后端端口改为 0.0.0.0:5000,支持局域网访问
This commit is contained in:
@@ -1,29 +1,20 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Health.Infrastructure.AI;
|
||||
|
||||
/// <summary>
|
||||
/// DeepSeek LLM 客户端(对话 + Tool Calling)
|
||||
/// </summary>
|
||||
public sealed class DeepSeekClient
|
||||
public sealed class DeepSeekClient(HttpClient http, IConfiguration config)
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _model;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public DeepSeekClient(HttpClient http, IConfiguration config)
|
||||
private readonly HttpClient _http = http;
|
||||
private readonly string _model = config["DEEPSEEK_MODEL"] ?? "deepseek-chat";
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
_http = http;
|
||||
_model = config["DEEPSEEK_MODEL"] ?? "deepseek-chat";
|
||||
_jsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 流式 Chat Completions
|
||||
@@ -96,22 +87,15 @@ public sealed class DeepSeekClient
|
||||
/// <summary>
|
||||
/// 千问 VL 视觉客户端(食物识别 + 报告解读)
|
||||
/// </summary>
|
||||
public sealed class QwenVisionClient
|
||||
public sealed class QwenVisionClient(HttpClient http, IConfiguration config)
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _model;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public QwenVisionClient(HttpClient http, IConfiguration config)
|
||||
private readonly HttpClient _http = http;
|
||||
private readonly string _model = config["QWEN_VISION_MODEL"] ?? "qwen-vl-max";
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
_http = http;
|
||||
_model = config["QWEN_VISION_MODEL"] ?? "qwen-vl-max";
|
||||
_jsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public async Task<ChatCompletionResponse> VisionAsync(
|
||||
string systemPrompt,
|
||||
@@ -1,33 +1,30 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Health.Infrastructure.AI;
|
||||
|
||||
/// <summary>
|
||||
/// OpenAI 兼容协议 HTTP 客户端,统一调用 DeepSeek / 千问 VL
|
||||
/// </summary>
|
||||
public sealed class OpenAiCompatibleClient
|
||||
public sealed class OpenAiCompatibleClient(string baseUrl, string apiKey, string model)
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _model;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public OpenAiCompatibleClient(string baseUrl, string apiKey, string model)
|
||||
private readonly HttpClient _http = CreateHttpClient(baseUrl, apiKey);
|
||||
private readonly string _model = model;
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
_http = new HttpClient
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private static HttpClient CreateHttpClient(string baseUrl, string apiKey)
|
||||
{
|
||||
var client = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"),
|
||||
Timeout = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
_model = model;
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1,5 +1,3 @@
|
||||
using Health.Domain.Enums;
|
||||
|
||||
namespace Health.Infrastructure.AI;
|
||||
|
||||
/// <summary>
|
||||
@@ -1,4 +1,3 @@
|
||||
using Health.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
@@ -7,9 +6,8 @@ namespace Health.Infrastructure.Data;
|
||||
/// <summary>
|
||||
/// 应用程序数据库上下文
|
||||
/// </summary>
|
||||
public sealed class AppDbContext : DbContext
|
||||
public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
// 核心业务表
|
||||
public DbSet<User> Users => Set<User>();
|
||||
@@ -1,5 +1,3 @@
|
||||
using Health.Domain.Entities;
|
||||
|
||||
namespace Health.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
@@ -1,5 +1,3 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Health.Infrastructure.Data;
|
||||
4
backend/src/Health.Infrastructure/GlobalUsings.cs
Normal file
4
backend/src/Health.Infrastructure/GlobalUsings.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
global using Health.Domain.Entities;
|
||||
global using Health.Domain.Enums;
|
||||
global using System.Text;
|
||||
global using System.Text.Json;
|
||||
@@ -3,25 +3,17 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Health.Infrastructure.Services;
|
||||
|
||||
/// <summary>
|
||||
/// JWT Token 生成与验证服务
|
||||
/// </summary>
|
||||
public sealed class JwtProvider
|
||||
public sealed class JwtProvider(IConfiguration configuration)
|
||||
{
|
||||
private readonly string _secret;
|
||||
private readonly string _issuer;
|
||||
private readonly string _audience;
|
||||
|
||||
public JwtProvider(IConfiguration configuration)
|
||||
{
|
||||
_secret = configuration["JWT_SECRET"] ?? "dev-secret-key-change-in-production-min-32-chars!!";
|
||||
_issuer = configuration["JWT_ISSUER"] ?? "health-manager";
|
||||
_audience = configuration["JWT_AUDIENCE"] ?? "health-manager-app";
|
||||
}
|
||||
private readonly string _secret = configuration["JWT_SECRET"] ?? "dev-secret-key-change-in-production-min-32-chars!!";
|
||||
private readonly string _issuer = configuration["JWT_ISSUER"] ?? "health-manager";
|
||||
private readonly string _audience = configuration["JWT_AUDIENCE"] ?? "health-manager-app";
|
||||
|
||||
/// <summary>
|
||||
/// 生成 access_token(30 分钟有效)
|
||||
@@ -1,21 +1,12 @@
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// 数据清理后台服务(每小时检查一次)
|
||||
/// </summary>
|
||||
public sealed class CleanupService : BackgroundService
|
||||
public sealed class CleanupService(IServiceScopeFactory scopeFactory, ILogger<CleanupService> logger) : BackgroundService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<CleanupService> _logger;
|
||||
|
||||
public CleanupService(IServiceScopeFactory scopeFactory, ILogger<CleanupService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
|
||||
private readonly ILogger<CleanupService> _logger = logger;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
@@ -1,23 +1,12 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// 用药提醒定时扫描服务(每分钟检查一次)
|
||||
/// </summary>
|
||||
public sealed class MedicationReminderService : BackgroundService
|
||||
public sealed class MedicationReminderService(IServiceScopeFactory scopeFactory, ILogger<MedicationReminderService> logger) : BackgroundService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<MedicationReminderService> _logger;
|
||||
|
||||
public MedicationReminderService(IServiceScopeFactory scopeFactory, ILogger<MedicationReminderService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
|
||||
private readonly ILogger<MedicationReminderService> _logger = logger;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
@@ -1,9 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Health.Infrastructure.AI;
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
@@ -135,7 +130,7 @@ public static class AiChatEndpoints
|
||||
await SseWriteAsync(http, new { action = "answer", data = content }, ct);
|
||||
}
|
||||
}
|
||||
catch { /* 跳过解析失败的 chunk */ }
|
||||
catch (JsonException) { /* 跳过解析失败的 chunk */ }
|
||||
}
|
||||
completedNormally = true;
|
||||
break;
|
||||
@@ -311,7 +306,7 @@ public static class AiChatEndpoints
|
||||
var sub = jwt.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
return sub != null && Guid.TryParse(sub, out var id) ? id : null;
|
||||
}
|
||||
catch { return null; }
|
||||
catch (Exception) { return null; }
|
||||
}
|
||||
|
||||
private static List<ToolDefinition> GetToolsForAgent(AgentType agentType) => agentType switch
|
||||
@@ -1,8 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Health.Domain.Entities;
|
||||
using Health.Infrastructure.Data;
|
||||
using Health.Infrastructure.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
@@ -1,8 +1,3 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
@@ -1,6 +1,3 @@
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
5
backend/src/Health.WebApi/GlobalUsings.cs
Normal file
5
backend/src/Health.WebApi/GlobalUsings.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
global using Health.Domain.Entities;
|
||||
global using Health.Domain.Enums;
|
||||
global using Health.Infrastructure.Data;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using System.Text.Json;
|
||||
@@ -1,21 +1,14 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Health.WebApi.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// 全局异常处理中间件——统一返回 {code, data, message} 格式
|
||||
/// </summary>
|
||||
public sealed class ExceptionMiddleware
|
||||
public sealed class ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionMiddleware> _logger;
|
||||
|
||||
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly RequestDelegate _next = next;
|
||||
private readonly ILogger<ExceptionMiddleware> _logger = logger;
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
@@ -5,7 +5,7 @@
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5277",
|
||||
"applicationUrl": "http://0.0.0.0:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7102;http://localhost:5277",
|
||||
"applicationUrl": "https://localhost:7102;http://0.0.0.0:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user