diff --git a/src/TelegramPanel.Core/Services/AccountManagementService.cs b/src/TelegramPanel.Core/Services/AccountManagementService.cs index b10c5bb..4bb0cdd 100644 --- a/src/TelegramPanel.Core/Services/AccountManagementService.cs +++ b/src/TelegramPanel.Core/Services/AccountManagementService.cs @@ -1,3 +1,6 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using TelegramPanel.Core.Interfaces; using TelegramPanel.Data.Entities; using TelegramPanel.Data.Repositories; @@ -11,15 +14,24 @@ public class AccountManagementService private readonly IAccountRepository _accountRepository; private readonly IChannelRepository _channelRepository; private readonly IGroupRepository _groupRepository; + private readonly ITelegramClientPool _clientPool; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; public AccountManagementService( IAccountRepository accountRepository, IChannelRepository channelRepository, - IGroupRepository groupRepository) + IGroupRepository groupRepository, + ITelegramClientPool clientPool, + IConfiguration configuration, + ILogger logger) { _accountRepository = accountRepository; _channelRepository = channelRepository; _groupRepository = groupRepository; + _clientPool = clientPool; + _configuration = configuration; + _logger = logger; } public async Task GetAccountAsync(int id) @@ -62,10 +74,157 @@ public class AccountManagementService var account = await _accountRepository.GetByIdAsync(id); if (account != null) { + try + { + // 先断开客户端,释放 session 文件锁 + await _clientPool.RemoveClientAsync(account.Id); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to remove client for account {AccountId} before deletion", account.Id); + } + + TryDeleteAccountFiles(account); await _accountRepository.DeleteAsync(account); } } + private void TryDeleteAccountFiles(Account account) + { + try + { + var candidates = ResolveSessionFileCandidates(account); + foreach (var path in candidates) + { + TryDeleteFile(path); + TryDeleteFile(BuildBackupPath(path, "sqlite.bak")); + TryDeleteFile(BuildBackupPath(path, "corrupt.bak")); + TryDeleteFile(BuildBackupPath(path, "bak")); + TryDeleteFile(BuildBackupPath(path, "wt")); + + var dir = Path.GetDirectoryName(path); + var name = Path.GetFileNameWithoutExtension(path); + if (!string.IsNullOrWhiteSpace(dir) && !string.IsNullOrWhiteSpace(name)) + TryDeleteFile(Path.Combine(dir, $"{name}.json")); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to delete account files for {Phone}", account.Phone); + } + } + + private IEnumerable ResolveSessionFileCandidates(Account account) + { + var set = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (!string.IsNullOrWhiteSpace(account.SessionPath)) + AddCandidatePath(account.SessionPath); + + // 常见命名:sessions/.session + var phoneDigits = NormalizePhone(account.Phone); + if (!string.IsNullOrWhiteSpace(phoneDigits)) + { + var sessionsPath = _configuration["Telegram:SessionsPath"] ?? "sessions"; + AddCandidatePath(Path.Combine(sessionsPath, $"{phoneDigits}.session")); + AddCandidatePath(Path.Combine("sessions", $"{phoneDigits}.session")); + AddCandidatePath(Path.Combine("src", "TelegramPanel.Web", "sessions", $"{phoneDigits}.session")); + } + + return set; + + void AddCandidatePath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return; + + try + { + if (Path.IsPathRooted(path)) + { + set.Add(Path.GetFullPath(path)); + return; + } + + // 1) 基于当前工作目录 + set.Add(Path.GetFullPath(path)); + + // 2) 基于仓库根目录(如果能找到 TelegramPanel.sln) + var repoRoot = TryFindRepoRoot(); + if (!string.IsNullOrWhiteSpace(repoRoot)) + set.Add(Path.GetFullPath(Path.Combine(repoRoot, path))); + } + catch + { + // ignore invalid paths + } + } + } + + private static string BuildBackupPath(string originalSessionPath, string suffix) + { + try + { + var fullPath = Path.GetFullPath(originalSessionPath); + var dir = Path.GetDirectoryName(fullPath) ?? Directory.GetCurrentDirectory(); + var name = Path.GetFileNameWithoutExtension(fullPath); + var ext = Path.GetExtension(fullPath); + return Path.Combine(dir, $"{name}.{suffix}{ext}"); + } + catch + { + return originalSessionPath; + } + } + + private void TryDeleteFile(string path) + { + try + { + if (string.IsNullOrWhiteSpace(path)) + return; + + var fullPath = Path.GetFullPath(path); + if (!File.Exists(fullPath)) + return; + + File.Delete(fullPath); + _logger.LogInformation("Deleted file: {Path}", fullPath); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to delete file: {Path}", path); + } + } + + private static string NormalizePhone(string? phone) + { + if (string.IsNullOrWhiteSpace(phone)) + return string.Empty; + + var digits = new char[phone.Length]; + var count = 0; + foreach (var ch in phone) + { + if (ch >= '0' && ch <= '9') + digits[count++] = ch; + } + return count == 0 ? string.Empty : new string(digits, 0, count); + } + + private static string? TryFindRepoRoot() + { + var current = Directory.GetCurrentDirectory(); + for (int i = 0; i < 10 && !string.IsNullOrWhiteSpace(current); i++) + { + if (File.Exists(Path.Combine(current, "TelegramPanel.sln"))) + return current; + current = Directory.GetParent(current)?.FullName; + } + + return null; + } + public async Task<(int channels, int groups)> GetAccountStatisticsAsync(int accountId) { var channels = await _channelRepository.GetByCreatorAccountAsync(accountId);