From 7c07bd9dbdad93bbefa94bff272cf0fb9a72dbcd Mon Sep 17 00:00:00 2001 From: meoacgx Date: Fri, 19 Dec 2025 21:00:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=98=8E=E7=A1=AE=E6=95=B0=E6=8D=AE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=90=AB=E4=B9=89=E5=B9=B6=E6=94=AF=E6=8C=81=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Pages/Channels.razor | 4 +- .../Components/Pages/Home.razor | 2 +- .../Components/Pages/Settings.razor | 99 +++++++++++++++---- src/TelegramPanel.Web/Program.cs | 1 + .../AccountDataAutoSyncBackgroundService.cs | 73 ++++++++++++++ 5 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs diff --git a/src/TelegramPanel.Web/Components/Pages/Channels.razor b/src/TelegramPanel.Web/Components/Pages/Channels.razor index 3ba83e9..2d8d559 100644 --- a/src/TelegramPanel.Web/Components/Pages/Channels.razor +++ b/src/TelegramPanel.Web/Components/Pages/Channels.razor @@ -34,11 +34,11 @@ - 同步当前账号 + 同步当前账号数据 - 同步全部账号 + 同步全部账号数据 diff --git a/src/TelegramPanel.Web/Components/Pages/Home.razor b/src/TelegramPanel.Web/Components/Pages/Home.razor index 15ef398..d58015c 100644 --- a/src/TelegramPanel.Web/Components/Pages/Home.razor +++ b/src/TelegramPanel.Web/Components/Pages/Home.razor @@ -79,7 +79,7 @@ - 同步数据 + 同步频道/群组数据 diff --git a/src/TelegramPanel.Web/Components/Pages/Settings.razor b/src/TelegramPanel.Web/Components/Pages/Settings.razor index b522c04..b1472f6 100644 --- a/src/TelegramPanel.Web/Components/Pages/Settings.razor +++ b/src/TelegramPanel.Web/Components/Pages/Settings.razor @@ -71,12 +71,24 @@ 数据同步 - + + + 同步说明:将每个活跃账号“创建的频道 + 创建的群组”拉取并写入本地数据库,用于频道/群组列表展示,以及批量任务自动选择执行账号。 +
+ 不包含:Telegram 状态检测(冻结/封号等)与验证码收取。 +
+
+ + @if (autoSync) { } + + 保存同步设置 + 立即同步 @@ -146,6 +158,8 @@ // 从配置文件加载默认设置 apiId = Configuration["Telegram:ApiId"] ?? ""; apiHash = Configuration["Telegram:ApiHash"] ?? ""; + autoSync = Configuration.GetValue("Sync:AutoSyncEnabled", false); + syncInterval = Configuration.GetValue("Sync:IntervalHours", 6); } private async Task SaveApiConfig() @@ -162,28 +176,14 @@ return; } - var localSettings = new - { - Telegram = new - { - ApiId = parsedApiId, - ApiHash = apiHash.Trim() - } - }; - try { var localConfigPath = LocalConfigPath; - var json = System.Text.Json.JsonSerializer.Serialize( - localSettings, - new System.Text.Json.JsonSerializerOptions { WriteIndented = true } - ); - - await System.IO.File.WriteAllTextAsync( - localConfigPath, - json, - new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false) - ); + var root = await LoadOrCreateLocalConfigAsync(); + var telegram = EnsureObject(root, "Telegram"); + telegram["ApiId"] = parsedApiId; + telegram["ApiHash"] = apiHash.Trim(); + await SaveLocalConfigAsync(root); // 配置变更后清理客户端缓存,确保后续操作使用新 ApiId/ApiHash await TelegramClientPool.RemoveAllClientsAsync(); @@ -198,6 +198,65 @@ } } + private async Task SaveSyncConfig() + { + if (autoSync && (syncInterval < 1 || syncInterval > 24)) + { + Snackbar.Add("同步间隔范围应为 1-24 小时", Severity.Error); + return; + } + + try + { + var root = await LoadOrCreateLocalConfigAsync(); + var sync = EnsureObject(root, "Sync"); + sync["AutoSyncEnabled"] = autoSync; + sync["IntervalHours"] = syncInterval; + await SaveLocalConfigAsync(root); + + Snackbar.Add("同步设置已保存(重启/自动重载后生效)", Severity.Success); + StateHasChanged(); + } + catch (Exception ex) + { + Snackbar.Add($"保存失败:{ex.Message}(路径:{LocalConfigPath})", Severity.Error); + } + } + + private async Task LoadOrCreateLocalConfigAsync() + { + var path = LocalConfigPath; + if (!System.IO.File.Exists(path)) + return new System.Text.Json.Nodes.JsonObject(); + + var json = await System.IO.File.ReadAllTextAsync(path); + if (string.IsNullOrWhiteSpace(json)) + return new System.Text.Json.Nodes.JsonObject(); + + return System.Text.Json.Nodes.JsonNode.Parse(json)?.AsObject() + ?? new System.Text.Json.Nodes.JsonObject(); + } + + private async Task SaveLocalConfigAsync(System.Text.Json.Nodes.JsonObject root) + { + var json = root.ToJsonString(new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); + await System.IO.File.WriteAllTextAsync( + LocalConfigPath, + json, + new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false) + ); + } + + private static System.Text.Json.Nodes.JsonObject EnsureObject(System.Text.Json.Nodes.JsonObject root, string key) + { + if (root[key] is System.Text.Json.Nodes.JsonObject obj) + return obj; + + var created = new System.Text.Json.Nodes.JsonObject(); + root[key] = created; + return created; + } + private Task SaveBatchConfig() { // TODO: 实现配置保存到文件 diff --git a/src/TelegramPanel.Web/Program.cs b/src/TelegramPanel.Web/Program.cs index 68302a5..90534b6 100644 --- a/src/TelegramPanel.Web/Program.cs +++ b/src/TelegramPanel.Web/Program.cs @@ -64,6 +64,7 @@ builder.Services.AddTelegramPanelCore(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHostedService(); +builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHttpClient(); diff --git a/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs b/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs new file mode 100644 index 0000000..fc9ab1f --- /dev/null +++ b/src/TelegramPanel.Web/Services/AccountDataAutoSyncBackgroundService.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace TelegramPanel.Web.Services; + +/// +/// 账号频道/群组数据自动同步(后台定时)。 +/// +public sealed class AccountDataAutoSyncBackgroundService : BackgroundService +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public AccountDataAutoSyncBackgroundService( + IServiceScopeFactory scopeFactory, + IConfiguration configuration, + ILogger logger) + { + _scopeFactory = scopeFactory; + _configuration = configuration; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // 延迟一点,避免与启动时 DB 迁移抢资源 + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + var enabled = _configuration.GetValue("Sync:AutoSyncEnabled", false); + var hours = _configuration.GetValue("Sync:IntervalHours", 6); + if (hours < 1) hours = 1; + if (hours > 24) hours = 24; + + if (!enabled) + { + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + continue; + } + + try + { + using var scope = _scopeFactory.CreateScope(); + var dataSync = scope.ServiceProvider.GetRequiredService(); + + _logger.LogInformation("Account auto sync started"); + var summary = await dataSync.SyncAllActiveAccountsAsync(stoppingToken); + if (summary.AccountFailures.Count > 0) + { + foreach (var f in summary.AccountFailures) + _logger.LogWarning("Account auto sync failed: {Phone} {Error}", f.Phone, f.Error); + } + + _logger.LogInformation("Account auto sync completed: {Channels} channels, {Groups} groups (failures={Failures})", + summary.TotalChannelsSynced, summary.TotalGroupsSynced, summary.AccountFailures.Count); + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + // ignore + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Account auto sync failed"); + } + + await Task.Delay(TimeSpan.FromHours(hours), stoppingToken); + } + } +} +