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);
+ }
+ }
+}
+