feat(kick): add module page and queue /api/kick

This commit is contained in:
meoacgx committed 2026-01-22 04:06:21 +08:00
1 parent 0401312ee3
commit f551da0a8b
7 files changed
+551 -156

No files matched your search

@@ -824,7 +824,8 @@ public sealed record BotAdminRights(
IReadOnlyList<long> channelTelegramIds,
long userId,
bool permanentBan,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
Func<long, string?, int, int, Task>? perChatCallback = null)
{
var bot = await _botManagement.GetBotAsync(botId)
?? throw new InvalidOperationException($"机器人不存在:{botId}");
@@ -936,6 +937,12 @@ public sealed record BotAdminRights(
}
}
if (perChatCallback != null)
{
failures.TryGetValue(chatId, out var err);
await perChatCallback(chatId, err, ok, failures.Count);
}
// 降速:避免 Bot API 429
if (delayMs > 0 && index < distinctIds.Count - 1)
{
@@ -0,0 +1,196 @@
@namespace TelegramPanel.Web.Components.ModulePages
@inject TelegramPanel.Core.Services.BotManagementService BotManagement
@inject TelegramPanel.Core.Services.BatchTaskManagementService TaskManagement
@inject ISnackbar Snackbar
@inject NavigationManager Navigation
@using System.Text.Json
@using TelegramPanel.Core.BatchTasks
@using TelegramPanel.Data.Entities
@using TelegramPanel.Web.ExternalApi
@using Microsoft.AspNetCore.Components
<MudText Typo="Typo.h4" Class="mb-4 tp-page-title">踢人/封禁</MudText>
<MudPaper Class="pa-4" Elevation="0" Outlined="true">
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
提交后会在后台执行,可到【任务中心】查看进度与结果。频道数量较多时可能需要较长时间。
</MudAlert>
<MudSelect T="int" Value="@selectedBotId" ValueChanged="@(async (int id) => await OnSelectedBotChanged(id))"
Label="选择机器人" Variant="Variant.Outlined" Dense="true" Disabled="@loading">
<MudSelectItem Value="0">全部机器人(谨慎)</MudSelectItem>
@foreach (var b in bots)
{
<MudSelectItem Value="@b.Id">@b.Name</MudSelectItem>
}
</MudSelect>
<MudNumericField T="long" @bind-Value="userId" Label="用户 ID(user_id)" Variant="Variant.Outlined" Dense="true"
Min="1" Disabled="@loading" />
<MudSwitch @bind-Value="permanentBan" Color="Color.Error" Disabled="@loading"
Label="永久封禁(否则仅踢出,可重新加入)" />
<MudSwitch @bind-Value="useAllChats" Disabled="@(loading || selectedBotId == 0)"
Label="使用全部频道/群组(关闭后可手动输入 chat_id 列表)" />
@if (!useAllChats && selectedBotId != 0)
{
<MudTextField @bind-Value="chatIdsText" Label="chat_id 列表(可选)" Variant="Variant.Outlined" Lines="3"
Placeholder="支持逗号/空格/换行分隔,例如:123,456,789"
Disabled="@loading" />
}
<MudStack Row="true" Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.PersonRemove"
Disabled="@(!CanSubmit)" OnClick="SubmitAsync">
提交任务
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Default" Disabled="@loading"
OnClick="@(() => Navigation.NavigateTo("/tasks"))">
打开任务中心
</MudButton>
</MudStack>
</MudStack>
</MudPaper>
@code
{
[Parameter] public string ModuleId { get; set; } = "";
[Parameter] public string PageKey { get; set; } = "";
private bool loading = true;
private List<Bot> bots = new();
private int selectedBotId = 0;
private long userId;
private bool permanentBan;
private bool useAllChats = true;
private string chatIdsText = "";
private bool CanSubmit => !loading && userId > 0 && (selectedBotId == 0 || bots.Any(x => x.Id == selectedBotId));
protected override async Task OnInitializedAsync()
{
try
{
bots = (await BotManagement.GetAllBotsAsync())
.Where(b => b.IsActive)
.OrderBy(b => b.Id)
.ToList();
}
catch (Exception ex)
{
Snackbar.Add($"加载机器人失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task OnSelectedBotChanged(int id)
{
selectedBotId = id;
if (selectedBotId == 0)
useAllChats = true;
await Task.CompletedTask;
}
private async Task SubmitAsync()
{
if (!CanSubmit)
return;
var botId = selectedBotId;
var runUseAllChats = botId == 0 ? true : useAllChats;
var chatIds = runUseAllChats ? new List<long>() : ParseChatIds(chatIdsText);
loading = true;
try
{
var total = await CountTargetsAsync(botId, runUseAllChats, chatIds);
if (total <= 0)
{
Snackbar.Add("未匹配到任何可操作的频道/群组(请先同步 Bot 频道数据,或检查 chat_id 列表)", Severity.Warning);
return;
}
var log = new KickTaskLog
{
ApiName = "模块页面",
BotId = botId,
UseAllChats = runUseAllChats,
ChatIds = chatIds.Where(x => x != 0).Distinct().OrderBy(x => x).ToList(),
UserId = userId,
PermanentBan = permanentBan,
RequestedAtUtc = DateTime.UtcNow
};
var task = new BatchTask
{
TaskType = BatchTaskTypes.ExternalApiKick,
Total = total,
Completed = 0,
Failed = 0,
Config = JsonSerializer.Serialize(log, new JsonSerializerOptions { WriteIndented = true })
};
var created = await TaskManagement.CreateTaskAsync(task);
Snackbar.Add($"已提交后台任务 #{created.Id}。请到【任务中心】查看进度。", Severity.Success);
Navigation.NavigateTo("/tasks");
}
catch (Exception ex)
{
Snackbar.Add($"提交失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task<int> CountTargetsAsync(int botId, bool runUseAllChats, IReadOnlyCollection<long> chatIds)
{
if (botId > 0)
{
var chats = (await BotManagement.GetChatsAsync(botId)).ToList();
if (!runUseAllChats && chatIds.Count > 0)
chats = chats.Where(c => chatIds.Contains(c.TelegramId)).ToList();
return chats.Count;
}
// botId==0: all bots (force useAllChats)
var botsList = (await BotManagement.GetAllBotsAsync())
.Where(b => b.IsActive)
.OrderBy(b => b.Id)
.ToList();
var total = 0;
foreach (var b in botsList)
{
var chats = await BotManagement.GetChatsAsync(b.Id);
total += chats.Count();
}
return total;
}
private static List<long> ParseChatIds(string raw)
{
raw = (raw ?? "").Trim();
if (raw.Length == 0)
return new List<long>();
var parts = raw
.Replace("\r", "\n", StringComparison.Ordinal)
.Split(new[] { ',', ';', ' ', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var ids = new List<long>();
foreach (var p in parts)
{
if (long.TryParse(p, out var v) && v != 0)
ids.Add(v);
}
return ids;
}
}
@@ -0,0 +1,244 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TelegramPanel.Core.BatchTasks;
using TelegramPanel.Core.Services;
using TelegramPanel.Core.Services.Telegram;
using TelegramPanel.Data.Entities;
using TelegramPanel.Modules;
namespace TelegramPanel.Web.ExternalApi;
public sealed class ExternalApiKickTaskHandler : IModuleTaskHandler
{
public string TaskType => BatchTaskTypes.ExternalApiKick;
public async Task ExecuteAsync(IModuleTaskExecutionHost host, CancellationToken cancellationToken)
{
var logger = host.Services.GetRequiredService<ILogger<ExternalApiKickTaskHandler>>();
var taskManagement = host.Services.GetRequiredService<BatchTaskManagementService>();
var botManagement = host.Services.GetRequiredService<BotManagementService>();
var botTelegram = host.Services.GetRequiredService<BotTelegramService>();
var rawConfig = (host.Config ?? "").Trim();
if (rawConfig.Length == 0)
throw new InvalidOperationException("任务缺少 Config");
KickTaskLog? input;
try
{
input = JsonSerializer.Deserialize<KickTaskLog>(rawConfig);
}
catch (Exception ex)
{
throw new InvalidOperationException($"任务 Config JSON 无效:{ex.Message}");
}
if (input == null)
throw new InvalidOperationException("任务 Config JSON 为空");
if (input.UserId <= 0)
throw new InvalidOperationException("任务 user_id 无效");
input.ChatIds ??= new List<long>();
var configuredBotId = input.BotId;
var useAllChats = configuredBotId == 0 ? true : input.UseAllChats;
var chatIdSet = new HashSet<long>(input.ChatIds.Where(x => x != 0));
var targets = await ResolveTargetsAsync(botManagement, configuredBotId, useAllChats, chatIdSet, cancellationToken);
if (targets.TotalChats == 0)
throw new InvalidOperationException("未匹配到任何可操作的频道/群组");
var results = new List<KickResultItem>(targets.TotalChats);
var completed = 0;
var failed = 0;
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var linkedToken = linkedCts.Token;
try
{
foreach (var group in targets.Groups)
{
linkedToken.ThrowIfCancellationRequested();
if (!await host.IsStillRunningAsync(linkedToken))
{
await taskManagement.UpdateTaskConfigAsync(host.TaskId, SerializeIndented(new KickTaskLog
{
ApiName = input.ApiName ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = chatIdSet.OrderBy(x => x).ToList(),
UserId = input.UserId,
PermanentBan = input.PermanentBan,
RequestedAtUtc = input.RequestedAtUtc,
Results = results,
Canceled = true
}));
return;
}
var baseCompleted = completed;
var baseFailed = failed;
var chatById = group.Chats.ToDictionary(c => c.TelegramId);
var recorded = new HashSet<long>();
try
{
_ = await botTelegram.BanChatMemberAsync(
botId: group.Bot.Id,
channelTelegramIds: group.Chats.Select(x => x.TelegramId).ToList(),
userId: input.UserId,
permanentBan: input.PermanentBan,
cancellationToken: linkedToken,
perChatCallback: async (chatId, error, okCount, failCount) =>
{
if (!recorded.Add(chatId))
return;
var ok = error == null;
results.Add(new KickResultItem(
ChatId: chatId.ToString(),
Title: chatById.TryGetValue(chatId, out var ch) ? ch.Title : chatId.ToString(),
Success: ok,
Error: error));
completed = baseCompleted + okCount;
failed = baseFailed + failCount;
await host.UpdateProgressAsync(completed, failed, linkedToken);
if (!await host.IsStillRunningAsync(linkedToken))
linkedCts.Cancel();
});
}
catch (OperationCanceledException) when (linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
// 用户主动取消(把任务状态改为非 running)
await taskManagement.UpdateTaskConfigAsync(host.TaskId, SerializeIndented(new KickTaskLog
{
ApiName = input.ApiName ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = chatIdSet.OrderBy(x => x).ToList(),
UserId = input.UserId,
PermanentBan = input.PermanentBan,
RequestedAtUtc = input.RequestedAtUtc,
Results = results,
Canceled = true
}));
return;
}
catch (Exception ex)
{
logger.LogWarning(ex, "ExternalApiKick failed on bot {BotId} (chats={Count})", group.Bot.Id, group.Chats.Count);
foreach (var chat in group.Chats)
{
if (!recorded.Add(chat.TelegramId))
continue;
results.Add(new KickResultItem(
ChatId: chat.TelegramId.ToString(),
Title: chat.Title,
Success: false,
Error: ex.Message));
failed++;
}
await host.UpdateProgressAsync(completed, failed, linkedToken);
}
}
}
catch (OperationCanceledException) when (linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
await taskManagement.UpdateTaskConfigAsync(host.TaskId, SerializeIndented(new KickTaskLog
{
ApiName = input.ApiName ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = chatIdSet.OrderBy(x => x).ToList(),
UserId = input.UserId,
PermanentBan = input.PermanentBan,
RequestedAtUtc = input.RequestedAtUtc,
Results = results,
Canceled = true
}));
return;
}
catch (Exception ex)
{
logger.LogWarning(ex, "ExternalApiKick failed (taskId={TaskId})", host.TaskId);
await taskManagement.UpdateTaskConfigAsync(host.TaskId, SerializeIndented(new KickTaskLog
{
ApiName = input.ApiName ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = chatIdSet.OrderBy(x => x).ToList(),
UserId = input.UserId,
PermanentBan = input.PermanentBan,
RequestedAtUtc = input.RequestedAtUtc,
Results = results,
Error = ex.Message
}));
throw;
}
await taskManagement.UpdateTaskConfigAsync(host.TaskId, SerializeIndented(new KickTaskLog
{
ApiName = input.ApiName ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = chatIdSet.OrderBy(x => x).ToList(),
UserId = input.UserId,
PermanentBan = input.PermanentBan,
RequestedAtUtc = input.RequestedAtUtc,
Results = results
}));
}
private static async Task<TargetResolution> ResolveTargetsAsync(
BotManagementService botManagement,
int configuredBotId,
bool useAllChats,
HashSet<long> configuredChatIds,
CancellationToken cancellationToken)
{
var bots = (await botManagement.GetAllBotsAsync())
.Where(b => b.IsActive)
.OrderBy(b => b.Id)
.ToList();
if (configuredBotId > 0)
bots = bots.Where(b => b.Id == configuredBotId).ToList();
var groups = new List<TargetGroup>();
var totalChats = 0;
foreach (var bot in bots)
{
cancellationToken.ThrowIfCancellationRequested();
var chats = (await botManagement.GetChatsAsync(bot.Id)).ToList();
if (!useAllChats && configuredChatIds.Count > 0)
chats = chats.Where(c => configuredChatIds.Contains(c.TelegramId)).ToList();
if (chats.Count == 0)
continue;
groups.Add(new TargetGroup(bot, chats));
totalChats += chats.Count;
}
return new TargetResolution(groups, totalChats);
}
private static string SerializeIndented<T>(T value)
{
return JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = true });
}
private sealed record TargetGroup(Bot Bot, List<BotChannel> Chats);
private sealed record TargetResolution(List<TargetGroup> Groups, int TotalChats);
}
+12 -152
View File
@@ -1,6 +1,5 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
@@ -8,7 +7,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using TelegramPanel.Core.Services;
using TelegramPanel.Core.Services.Telegram;
using TelegramPanel.Core.BatchTasks;
using TelegramPanel.Data.Entities;
@@ -27,7 +25,6 @@ public static class KickApi
HttpContext http,
IConfiguration configuration,
BotManagementService botManagement,
BotTelegramService botTelegram,
BatchTaskManagementService taskManagement,
ILoggerFactory loggerFactory,
CancellationToken cancellationToken)
@@ -86,10 +83,10 @@ public static class KickApi
false,
"未配置任何可操作的频道/群组(请先在页面选择机器人与频道/群组)",
new KickSummary(0, 0, 0),
Array.Empty<KickResultItem>()));
Array.Empty<KickResultItem>()));
}
// 记录到任务中心(即使外部调用返回了,也能追溯这次操作)
// 提交到任务中心,由后台执行(避免反代/CF 524 超时)
var createdTask = await taskManagement.CreateTaskAsync(new BatchTask
{
TaskType = BatchTaskTypes.ExternalApiKick,
@@ -108,120 +105,17 @@ public static class KickApi
})
});
await taskManagement.StartTaskAsync(createdTask.Id);
logger.LogInformation("Kick task queued: taskId={TaskId} api={ApiName} botId={BotId} total={Total} userId={UserId} permanent={Permanent}",
createdTask.Id, matched.Name ?? "", configuredBotId, targets.TotalChats, request.UserId, permanentBan);
var results = new List<KickResultItem>(targets.TotalChats);
var completed = 0;
var failedCount = 0;
try
{
foreach (var group in targets.Groups)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var banResult = await botTelegram.BanChatMemberAsync(
botId: group.Bot.Id,
channelTelegramIds: group.Chats.Select(x => x.TelegramId).ToList(),
userId: request.UserId,
permanentBan: permanentBan,
cancellationToken: cancellationToken);
var failures = banResult.Failures;
foreach (var chat in group.Chats)
{
failures.TryGetValue(chat.TelegramId, out var err);
var ok = err == null;
results.Add(new KickResultItem(
ChatId: chat.TelegramId.ToString(),
Title: chat.Title,
Success: ok,
Error: err));
if (ok) completed++; else failedCount++;
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Kick API failed on bot {BotId} (chats={Count})", group.Bot.Id, group.Chats.Count);
foreach (var chat in group.Chats)
{
results.Add(new KickResultItem(
ChatId: chat.TelegramId.ToString(),
Title: chat.Title,
Success: false,
Error: ex.Message));
failedCount++;
}
}
await taskManagement.UpdateTaskProgressAsync(createdTask.Id, completed, failedCount);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
await taskManagement.UpdateTaskProgressAsync(createdTask.Id, completed, failedCount == 0 ? 1 : failedCount);
await taskManagement.UpdateTaskConfigAsync(createdTask.Id, SerializeIndented(new KickTaskLog
{
ApiName = matched.Name ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = configuredChatSet.OrderBy(x => x).ToList(),
UserId = request.UserId,
PermanentBan = permanentBan,
RequestedAtUtc = DateTime.UtcNow,
Results = results,
Canceled = true
}));
await taskManagement.CompleteTaskAsync(createdTask.Id, success: false);
throw;
}
catch (Exception ex)
{
logger.LogWarning(ex, "Kick API failed (taskId={TaskId})", createdTask.Id);
await taskManagement.UpdateTaskProgressAsync(createdTask.Id, completed, failedCount == 0 ? 1 : failedCount);
await taskManagement.UpdateTaskConfigAsync(createdTask.Id, SerializeIndented(new KickTaskLog
{
ApiName = matched.Name ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = configuredChatSet.OrderBy(x => x).ToList(),
UserId = request.UserId,
PermanentBan = permanentBan,
RequestedAtUtc = DateTime.UtcNow,
Results = results,
Error = ex.Message
}));
await taskManagement.CompleteTaskAsync(createdTask.Id, success: false);
throw;
}
var okCount = results.Count(x => x.Success);
var total = results.Count;
var failedTotal = total - okCount;
var actionText = permanentBan ? "Banned" : "Kicked";
var message = $"{actionText} user {request.UserId} from {okCount}/{total} chats";
await taskManagement.UpdateTaskConfigAsync(createdTask.Id, SerializeIndented(new KickTaskLog
{
ApiName = matched.Name ?? "",
BotId = configuredBotId,
UseAllChats = useAllChats,
ChatIds = configuredChatSet.OrderBy(x => x).ToList(),
UserId = request.UserId,
PermanentBan = permanentBan,
RequestedAtUtc = DateTime.UtcNow,
Results = results
}));
await taskManagement.CompleteTaskAsync(createdTask.Id, success: failedTotal == 0);
return Results.Ok(new KickResponse(
Success: true,
Message: message,
Summary: new KickSummary(total, okCount, failedTotal),
Results: results));
return Results.Json(
new KickResponse(
Success: true,
Message: $"已提交后台任务 #{createdTask.Id},请到面板【任务中心】查看进度与结果。",
Summary: new KickSummary(targets.TotalChats, 0, 0),
Results: Array.Empty<KickResultItem>(),
TaskId: createdTask.Id),
statusCode: StatusCodes.Status202Accepted);
}
private static async Task<TargetResolution> ResolveTargetsAsync(
@@ -280,41 +174,7 @@ public static class KickApi
return JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = true });
}
public sealed record KickRequest(
[property: JsonPropertyName("user_id")] long UserId,
[property: JsonPropertyName("permanent_ban")] bool? PermanentBan = null);
public sealed record KickResponse(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("message")] string Message,
[property: JsonPropertyName("summary")] KickSummary Summary,
[property: JsonPropertyName("results")] IReadOnlyList<KickResultItem> Results);
public sealed record KickSummary(
[property: JsonPropertyName("total")] int Total,
[property: JsonPropertyName("success")] int Success,
[property: JsonPropertyName("failed")] int Failed);
public sealed record KickResultItem(
[property: JsonPropertyName("chat_id")] string ChatId,
[property: JsonPropertyName("title")] string Title,
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("error")] string? Error);
private sealed record TargetGroup(Bot Bot, List<BotChannel> Chats);
private sealed record TargetResolution(List<TargetGroup> Groups, int TotalChats);
private sealed class KickTaskLog
{
public string ApiName { get; set; } = "";
public int BotId { get; set; }
public bool UseAllChats { get; set; }
public List<long> ChatIds { get; set; } = new();
public long UserId { get; set; }
public bool PermanentBan { get; set; }
public DateTime RequestedAtUtc { get; set; }
public bool Canceled { get; set; }
public string? Error { get; set; }
public List<KickResultItem>? Results { get; set; }
}
}
@@ -0,0 +1,62 @@
using System.Text.Json.Serialization;
namespace TelegramPanel.Web.ExternalApi;
public sealed record KickRequest(
[property: JsonPropertyName("user_id")] long UserId,
[property: JsonPropertyName("permanent_ban")] bool? PermanentBan = null);
public sealed record KickResponse(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("message")] string Message,
[property: JsonPropertyName("summary")] KickSummary Summary,
[property: JsonPropertyName("results")] IReadOnlyList<KickResultItem> Results,
[property: JsonPropertyName("task_id")] int? TaskId = null);
public sealed record KickSummary(
[property: JsonPropertyName("total")] int Total,
[property: JsonPropertyName("success")] int Success,
[property: JsonPropertyName("failed")] int Failed);
public sealed record KickResultItem(
[property: JsonPropertyName("chat_id")] string ChatId,
[property: JsonPropertyName("title")] string Title,
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("error")] string? Error);
public sealed class KickTaskLog
{
[JsonPropertyName("api_name")]
public string ApiName { get; set; } = "";
/// <summary>
/// 0=all bots
/// </summary>
[JsonPropertyName("bot_id")]
public int BotId { get; set; }
[JsonPropertyName("use_all_chats")]
public bool UseAllChats { get; set; }
[JsonPropertyName("chat_ids")]
public List<long> ChatIds { get; set; } = new();
[JsonPropertyName("user_id")]
public long UserId { get; set; }
[JsonPropertyName("permanent_ban")]
public bool PermanentBan { get; set; }
[JsonPropertyName("requested_at_utc")]
public DateTime RequestedAtUtc { get; set; }
[JsonPropertyName("canceled")]
public bool Canceled { get; set; }
[JsonPropertyName("error")]
public string? Error { get; set; }
[JsonPropertyName("results")]
public List<KickResultItem>? Results { get; set; }
}
@@ -2,12 +2,14 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MudBlazor;
using TelegramPanel.Modules;
using TelegramPanel.Web.Components.ModulePages;
using TelegramPanel.Web.ExternalApi;
namespace TelegramPanel.Web.Modules.BuiltIn;
public sealed class KickApiModule : ITelegramPanelModule, IModuleApiProvider
public sealed class KickApiModule : ITelegramPanelModule, IModuleApiProvider, IModuleUiProvider
{
public KickApiModule(string version)
{
@@ -25,7 +27,7 @@ public sealed class KickApiModule : ITelegramPanelModule, IModuleApiProvider
public void ConfigureServices(IServiceCollection services, ModuleHostContext context)
{
// built-in:无需额外注入
services.AddScoped<IModuleTaskHandler, ExternalApiKickTaskHandler>();
}
public void MapEndpoints(IEndpointRouteBuilder endpoints, ModuleHostContext context)
@@ -49,4 +51,22 @@ public sealed class KickApiModule : ITelegramPanelModule, IModuleApiProvider
Order = 10
};
}
public IEnumerable<ModuleNavItem> GetNavItems(ModuleHostContext context)
{
yield break;
}
public IEnumerable<ModulePageDefinition> GetPages(ModuleHostContext context)
{
yield return new ModulePageDefinition
{
Key = "kick",
Title = "踢人/封禁",
Icon = Icons.Material.Filled.PersonRemove,
Group = "外部 API",
Order = 10,
ComponentType = typeof(KickApiPage).AssemblyQualifiedName ?? ""
};
}
}
+7 -1
View File
@@ -262,7 +262,13 @@ if (serilogEnabled)
{
loggerConfig = loggerConfig
.ReadFrom.Configuration(builder.Configuration)
.WriteTo.File("logs/telegram-panel-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: retainedFileCountLimit);
// buffered=true:降低磁盘抖动/IO 阻塞导致的请求卡顿风险(尤其是低配 VPS/挂载卷/杀软扫描场景)
.WriteTo.File(
"logs/telegram-panel-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: retainedFileCountLimit,
buffered: true,
flushToDiskInterval: TimeSpan.FromSeconds(1));
}
else
{