明确数据同步含义并支持后台自动同步

This commit is contained in:
meoacgx committed 2025-12-19 21:00:28 +08:00
1 parent 66cb199d4a
commit 7c07bd9dbd
5 files changed
+156 -23

No files matched your search

@@ -34,11 +34,11 @@
</MudSelect>
<MudButton Variant="Variant.Outlined" Color="Color.Info" StartIcon="@Icons.Material.Filled.Sync" Class="mr-2"
Disabled="@(loading || filterAccount <= 0)" OnClick="SyncCurrentAccountChannels">
同步当前账号
同步当前账号数据
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Info" StartIcon="@Icons.Material.Filled.Sync" Class="mr-2"
Disabled="@loading" OnClick="SyncAllAccountsChannels">
同步全部账号
同步全部账号数据
</MudButton>
<MudTextField @bind-Value="searchString" Placeholder="搜索频道..." Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" />
@@ -79,7 +79,7 @@
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Sync"
Disabled="@_syncing"
OnClick="@(async (MouseEventArgs _) => await SyncAllAccounts())">
同步数据
同步频道/群组数据
</MudButton>
</MudStack>
</MudPaper>
@@ -71,12 +71,24 @@
<MudText Typo="Typo.h6">数据同步</MudText>
</MudCardHeader>
<MudCardContent>
<MudSwitch @bind-Value="autoSync" Label="自动同步频道/群组信息" Color="Color.Primary" />
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true" Class="mb-3">
<MudText Typo="Typo.body2">
<strong>同步说明:</strong>将每个活跃账号“创建的频道 + 创建的群组”拉取并写入本地数据库,用于频道/群组列表展示,以及批量任务自动选择执行账号。
<br />
<strong>不包含:</strong>Telegram 状态检测(冻结/封号等)与验证码收取。
</MudText>
</MudAlert>
<MudSwitch @bind-Value="autoSync" Label="自动同步频道/群组数据(后台)" Color="Color.Primary" />
@if (autoSync)
{
<MudNumericField @bind-Value="syncInterval" Label="同步间隔 (小时)" Variant="Variant.Outlined" Class="mt-3"
Min="1" Max="24" />
}
<MudButton Variant="Variant.Outlined" Color="Color.Primary" FullWidth="true" Class="mt-3"
OnClick="@(async (MouseEventArgs _) => await SaveSyncConfig())">
保存同步设置
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Info" FullWidth="true" Class="mt-4"
OnClick="@(async (MouseEventArgs _) => await SyncNow())">
立即同步
@@ -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<System.Text.Json.Nodes.JsonObject> 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: 实现配置保存到文件
+1
View File
@@ -64,6 +64,7 @@ builder.Services.AddTelegramPanelCore();
builder.Services.AddScoped<AccountExportService>();
builder.Services.AddScoped<DataSyncService>();
builder.Services.AddHostedService<BatchTaskBackgroundService>();
builder.Services.AddHostedService<AccountDataAutoSyncBackgroundService>();
builder.Services.AddHostedService<BotAutoSyncBackgroundService>();
builder.Services.AddHttpClient<TelegramBotApiClient>();
@@ -0,0 +1,73 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace TelegramPanel.Web.Services;
/// <summary>
/// 账号频道/群组数据自动同步(后台定时)。
/// </summary>
public sealed class AccountDataAutoSyncBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<AccountDataAutoSyncBackgroundService> _logger;
public AccountDataAutoSyncBackgroundService(
IServiceScopeFactory scopeFactory,
IConfiguration configuration,
ILogger<AccountDataAutoSyncBackgroundService> 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<DataSyncService>();
_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);
}
}
}