14868c45c7
1. 重构用户与任务实体:UserEntity实现IUser<Guid>,TaskEntity继承ABP风格FullAuditedEntityWithUser,主键从int改为Guid 2. 新增云同步代理系统:嵌入式WebServer支持CloudSyncProxy转发云同步请求,新增配置API与持久化 3. 完善前端适配:新增Guid工具函数,更新任务类型定义与API交互逻辑,调整云同步设置弹窗适配本地代理 4. 文档与配置优化:更新文档结构,新增部署文档、版本记录,统一各项目配置项 5. 补充测试与迁移:新增单元测试,更新EF Core数据库迁移快照
208 lines
5.8 KiB
C#
208 lines
5.8 KiB
C#
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Data.Sqlite;
|
|
using Hua.Todo.Application;
|
|
using Hua.Todo.Application.CloudSync;
|
|
using Hua.Todo.Application.Data;
|
|
using Hua.Todo.Maui.Models;
|
|
using Hua.Todo.Maui.Services;
|
|
using Hua.Todo.Maui.Services.Platforms;
|
|
|
|
namespace Hua.Todo.Maui;
|
|
|
|
/// <summary>
|
|
/// MAUI 程序启动类
|
|
/// </summary>
|
|
public static partial class MauiProgram
|
|
{
|
|
/// <summary>
|
|
/// 创建并配置 MAUI 应用程序
|
|
/// </summary>
|
|
public static MauiApp CreateMauiApp()
|
|
{
|
|
ConfigurePlatformWebViewContainer();
|
|
var builder = MauiApp.CreateBuilder();
|
|
builder
|
|
.UseMauiApp<App>()
|
|
.ConfigureFonts(fonts =>
|
|
{
|
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
|
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
|
});
|
|
|
|
// 加载应用程序配置
|
|
var appSettings = LoadAppSettings();
|
|
|
|
// 如果未提供连接字符串,则设置默认的 SQLite 连接字符串
|
|
if (string.IsNullOrEmpty(appSettings.WebServer.ConnectionString))
|
|
{
|
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
var dbDir = Path.Combine(localAppData, "Hua.Todo");
|
|
if (!Directory.Exists(dbDir))
|
|
{
|
|
Directory.CreateDirectory(dbDir);
|
|
}
|
|
var dbPath = Path.Combine(dbDir, "Hua.Todo.db");
|
|
appSettings.WebServer.ConnectionString = $"Data Source={dbPath};Cache=Shared";
|
|
}
|
|
|
|
builder.Services.AddSingleton(appSettings);
|
|
var connectionString = appSettings.WebServer.ConnectionString;
|
|
|
|
// 注册应用服务
|
|
builder.Services.AddApplicationServices(connectionString);
|
|
builder.Services.AddTransient<Views.MainPage>();
|
|
|
|
// 注册热键设置服务
|
|
builder.Services.AddSingleton<IHotKeySettingsService>(sp =>
|
|
new HotKeySettingsService(sp.GetRequiredService<AppSettings>()));
|
|
|
|
// 注册全局热键服务(工厂模式)
|
|
builder.Services.AddSingleton<IGlobalHotKeyService>(sp => GlobalHotKeyServiceFactory.Create());
|
|
|
|
// 注册系统托盘服务(平台相关)
|
|
builder.Services.AddSingleton<ISystemTrayService>(sp =>
|
|
{
|
|
#if WINDOWS
|
|
return new WindowsSystemTrayService();
|
|
#else
|
|
return new NullSystemTrayService();
|
|
#endif
|
|
});
|
|
|
|
// 注册嵌入式 Web 服务器(平台相关)
|
|
#if WINDOWS
|
|
// Windows 平台下嵌入式 WebServer 的启用策略:
|
|
// - 静态托管模式(IsUsingStatic=true):由 MAUI 内置 WebServer 提供 wwwroot 与本地 API。
|
|
// - 开发三件套模式(IsUsingStatic=false,前端走 Vite):API 由独立 Host(5173) 提供,避免在 MAUI 内启动 WebServer 导致注入覆盖前端代理配置。
|
|
if (appSettings.WebServer.IsUsingStatic)
|
|
{
|
|
builder.Services.AddSingleton<IEmbeddedWebServerService, EmbeddedWebServerService>();
|
|
}
|
|
else
|
|
{
|
|
builder.Services.AddSingleton<IEmbeddedWebServerService, NoopEmbeddedWebServerService>();
|
|
}
|
|
#elif ANDROID
|
|
builder.Services.AddSingleton<IEmbeddedWebServerService, MobileEmbeddedWebServerService>();
|
|
#else
|
|
builder.Services.AddSingleton<IEmbeddedWebServerService, NoopEmbeddedWebServerService>();
|
|
#endif
|
|
|
|
#if DEBUG
|
|
builder.Logging.AddDebug();
|
|
#endif
|
|
|
|
var app = builder.Build();
|
|
|
|
// 异步初始化数据库和 Web 服务器
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
// 优先启动内嵌 WebServer,确保 WebView 加载时不再 ERR_CONNECTION_REFUSED。
|
|
await StartWebServer(app.Services);
|
|
InitializeDatabase(app.Services, connectionString);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"App initialization failed: {ex}");
|
|
}
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
static partial void ConfigurePlatformWebViewContainer();
|
|
|
|
/// <summary>
|
|
/// 从 appsettings.json 加载配置
|
|
/// </summary>
|
|
private static AppSettings LoadAppSettings()
|
|
{
|
|
try
|
|
{
|
|
using var stream = FileSystem.OpenAppPackageFileAsync("appsettings.json").GetAwaiter().GetResult();
|
|
using var reader = new StreamReader(stream);
|
|
var json = reader.ReadToEnd();
|
|
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
|
}
|
|
catch
|
|
{
|
|
try
|
|
{
|
|
var settingsPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
|
if (!File.Exists(settingsPath))
|
|
{
|
|
return new AppSettings();
|
|
}
|
|
|
|
var json = File.ReadAllText(settingsPath);
|
|
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
|
}
|
|
catch
|
|
{
|
|
return new AppSettings();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化数据库(执行迁移)
|
|
/// </summary>
|
|
private static void InitializeDatabase(IServiceProvider services, string connectionString)
|
|
{
|
|
using var scope = services.CreateScope();
|
|
|
|
try
|
|
{
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<TodoDbContext>();
|
|
|
|
var sqliteBuilder = new SqliteConnectionStringBuilder(connectionString);
|
|
var actualDbPath = sqliteBuilder.DataSource;
|
|
if (!string.IsNullOrEmpty(actualDbPath))
|
|
{
|
|
var dbDir = Path.GetDirectoryName(actualDbPath);
|
|
if (!string.IsNullOrEmpty(dbDir) && !Directory.Exists(dbDir))
|
|
{
|
|
Directory.CreateDirectory(dbDir);
|
|
}
|
|
}
|
|
|
|
// 确保使用 WAL 模式以避免锁定问题
|
|
dbContext.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL;");
|
|
dbContext.Database.Migrate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Database initialization failed: {ex.Message}");
|
|
|
|
try
|
|
{
|
|
var context = scope.ServiceProvider.GetRequiredService<TodoDbContext>();
|
|
context.Database.EnsureCreated();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 启动嵌入式 Web 服务器
|
|
/// </summary>
|
|
private static async Task StartWebServer(IServiceProvider services)
|
|
{
|
|
try
|
|
{
|
|
var webServer = services.GetRequiredService<IEmbeddedWebServerService>();
|
|
await webServer.StartAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Web server start failed: {ex}");
|
|
}
|
|
}
|
|
}
|