- 所有空 catch 块补全日志记录,统一使用 Serilog/AppLog - 按场景分级:Error(意外失败)、Warning(次要问题)、Information(预期内) - 端口 HttpPort/HttpsPort 抽离到 appsettings.json Server 配置节 - QrCodeService 通过 IConfiguration 读取端口,消除硬编码 - 前端通过 Vite proxy 转发 /api,http.ts 统一使用 origin 地址 - 移除所有 Debug.WriteLine 和 Serilog.Log.Debug 日志 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
226 lines
8.2 KiB
C#
226 lines
8.2 KiB
C#
using FileShare_Common.Infrastructure;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||
using Microsoft.EntityFrameworkCore.Migrations;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace FileShare_EFCore.Database
|
||
{
|
||
/// <summary>
|
||
/// 数据库管理器 —— 负责连接测试、自动迁移、种子数据、版本检查。
|
||
/// 在应用启动时调用,确保数据库结构与应用代码同步。
|
||
/// </summary>
|
||
public class DatabaseManager<TContext> where TContext : AppDbContext
|
||
{
|
||
/// <summary>
|
||
/// 数据库上下文实例。
|
||
/// </summary>
|
||
private readonly TContext _context;
|
||
/// <summary>
|
||
/// 数据库配置。
|
||
/// </summary>
|
||
private readonly DatabaseConfiguration _config;
|
||
/// <summary>
|
||
/// DI 服务提供程序(可选,用于种子数据中解析服务)。
|
||
/// </summary>
|
||
private readonly IServiceProvider? _serviceProvider;
|
||
|
||
/// <summary>
|
||
/// 初始化数据库管理器。
|
||
/// </summary>
|
||
/// <param name="context">数据库上下文。</param>
|
||
/// <param name="config">数据库配置。</param>
|
||
/// <param name="serviceProvider">可选的 DI 容器。</param>
|
||
public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null)
|
||
{
|
||
_context = context;
|
||
_config = config;
|
||
_serviceProvider = serviceProvider;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化数据库:测试连接 → 自动迁移 → 种子数据。
|
||
/// </summary>
|
||
public async Task InitializeAsync(Action<TContext, IServiceProvider?>? seeder = null)
|
||
{
|
||
AppLog.Information(
|
||
"正在初始化数据库 Provider={Provider}, AppVersion={AppVersion}",
|
||
_config.Provider,
|
||
GetApplicationVersion());
|
||
|
||
// 1. 自动迁移(如果启用)。MigrateAsync 会按迁移历史顺序执行全部待处理迁移,
|
||
// 支持用户从较旧软件版本直接升级到当前版本。
|
||
if (_config.AutoMigrate)
|
||
{
|
||
if (_config.RecreateDatabase)
|
||
{
|
||
AppLog.Warning(
|
||
"RecreateDatabase=true,将删除并重建当前连接指向的数据库。Provider={Provider}",
|
||
_config.Provider);
|
||
|
||
await _context.Database.EnsureDeletedAsync();
|
||
}
|
||
|
||
await MigrateAsync();
|
||
}
|
||
else
|
||
{
|
||
var canConnect = await CanConnectAsync();
|
||
if (!canConnect)
|
||
{
|
||
throw new InvalidOperationException(
|
||
$"无法连接到数据库 [{_config.Provider}],请检查连接字符串和数据库服务状态。");
|
||
}
|
||
}
|
||
|
||
// 2. 种子数据
|
||
if (seeder != null)
|
||
{
|
||
seeder(_context, _serviceProvider);
|
||
await _context.SaveChangesAsync();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试数据库连接是否正常。
|
||
/// </summary>
|
||
public async Task<bool> CanConnectAsync()
|
||
{
|
||
try
|
||
{
|
||
return await _context.Database.CanConnectAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppLog.Warning("数据库连接测试失败 Provider={Provider} Error={Error}", _config.Provider, ex.Message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行待处理的迁移。
|
||
/// 使用 EF Core 原生迁移机制,自动检测并应用 Schema 变更。
|
||
/// </summary>
|
||
public async Task MigrateAsync()
|
||
{
|
||
try
|
||
{
|
||
var appliedMigrations = (await _context.Database.GetAppliedMigrationsAsync()).ToList();
|
||
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
|
||
|
||
if (pendingMigrations.Any())
|
||
{
|
||
if (appliedMigrations.Count == 0)
|
||
{
|
||
AppLog.Information(
|
||
"未检测到已应用迁移,将按当前 Provider={Provider} 从 0 构建完整表结构",
|
||
_config.Provider);
|
||
}
|
||
|
||
AppLog.Information(
|
||
"当前已应用 {AppliedCount} 个迁移,检测到 {PendingCount} 个待执行迁移: {Migrations}",
|
||
appliedMigrations.Count,
|
||
pendingMigrations.Count(),
|
||
string.Join(", ", pendingMigrations));
|
||
|
||
await _context.Database.MigrateAsync();
|
||
|
||
AppLog.Information("数据库迁移完成({Count} 个迁移已应用)", pendingMigrations.Count());
|
||
}
|
||
else
|
||
{
|
||
AppLog.Information("数据库已是最新版本,无需迁移");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppLog.Error(ex, "数据库迁移失败");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前应用程序的版本号,优先读取 AssemblyInformationalVersion,回退到 AssemblyVersion。
|
||
/// </summary>
|
||
/// <returns>应用程序版本字符串。</returns>
|
||
private static string GetApplicationVersion()
|
||
{
|
||
var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly;
|
||
return assembly
|
||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||
?.InformationalVersion
|
||
?? assembly.GetName().Version?.ToString()
|
||
?? "unknown";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取数据库当前版本信息。
|
||
/// </summary>
|
||
public async Task<DatabaseVersionInfo> GetVersionInfoAsync()
|
||
{
|
||
var appliedMigrations = await _context.Database.GetAppliedMigrationsAsync();
|
||
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
|
||
|
||
return new DatabaseVersionInfo
|
||
{
|
||
Provider = _config.Provider.ToString(),
|
||
AppliedMigrations = appliedMigrations.ToList(),
|
||
PendingMigrations = pendingMigrations.ToList(),
|
||
IsLatest = !pendingMigrations.Any(),
|
||
CanConnect = await CanConnectAsync(),
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成从指定迁移到最新版本的 SQL 脚本(用于生产环境审计)。
|
||
/// </summary>
|
||
public string GenerateMigrationScript(string? fromMigration = null)
|
||
{
|
||
var migrator = _context.GetService<IMigrator>();
|
||
return fromMigration is null
|
||
? migrator.GenerateScript()
|
||
: migrator.GenerateScript(fromMigration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保数据库已创建(不执行迁移,适用于简单场景)。
|
||
/// </summary>
|
||
public bool EnsureCreated()
|
||
{
|
||
return _context.Database.EnsureCreated();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 数据库版本信息 DTO。
|
||
/// </summary>
|
||
public class DatabaseVersionInfo
|
||
{
|
||
/// <summary>
|
||
/// 获取或设置数据库提供程序名称。
|
||
/// </summary>
|
||
public string Provider { get; set; } = string.Empty;
|
||
/// <summary>
|
||
/// 获取或设置已应用的迁移列表。
|
||
/// </summary>
|
||
public List<string> AppliedMigrations { get; set; } = new();
|
||
/// <summary>
|
||
/// 获取或设置待应用的迁移列表。
|
||
/// </summary>
|
||
public List<string> PendingMigrations { get; set; } = new();
|
||
/// <summary>
|
||
/// 获取或设置是否为最新版本。
|
||
/// </summary>
|
||
public bool IsLatest { get; set; }
|
||
/// <summary>
|
||
/// 获取或设置数据库是否可连接。
|
||
/// </summary>
|
||
public bool CanConnect { get; set; }
|
||
}
|
||
}
|