明确数据同步含义并支持后台自动同步
This commit is contained in:
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: 实现配置保存到文件
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user