修复:手机号登录验证码发送失败与 session 占用
- 增加 ApiHash 32 位十六进制校验\n- 登录失败/未知状态自动释放临时 Telegram client,避免 session 文件被占用\n- 优化系统设置提示与 README 说明
This commit is contained in:
1 parent
e60b5fec4d
commit
eb9abc71be
5 files changed
+171
-27
No files matched your search
@@ -63,6 +63,7 @@ docker compose up -d --build
|
||||
到 https://my.telegram.org/apps **用任意一个 Telegram 账号申请一次** `api_id` / `api_hash`,然后在面板「系统设置」里保存即可。
|
||||
|
||||
> 说明:**不需要每个账号都申请**,全站共用这一对 `api_id` / `api_hash` 就能工作。
|
||||
> 提示:`api_hash` 是 **32 位十六进制字符串(0-9a-f)**,请不要填 Token/用户名/URL 等其它值。
|
||||
|
||||
🏪 推荐 Telegram 开发者 API 购买:
|
||||
https://faka.boxmoe.eu.org/
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using TelegramPanel.Core.Interfaces;
|
||||
using TelegramPanel.Core.Models;
|
||||
using TelegramPanel.Core.Utils;
|
||||
using AccountStatus = TelegramPanel.Core.Interfaces.AccountStatus;
|
||||
|
||||
namespace TelegramPanel.Core.Services.Telegram;
|
||||
@@ -27,13 +28,16 @@ public class AccountService : IAccountService
|
||||
|
||||
public async Task<LoginResult> StartLoginAsync(int accountId, string phone)
|
||||
{
|
||||
if (!int.TryParse(_configuration["Telegram:ApiId"], out var apiId) || apiId <= 0
|
||||
|| string.IsNullOrWhiteSpace(_configuration["Telegram:ApiHash"]))
|
||||
if (!int.TryParse(_configuration["Telegram:ApiId"], out var apiId) || apiId <= 0)
|
||||
{
|
||||
return new LoginResult(false, null, "请先在【系统设置】中配置全局 Telegram API(ApiId/ApiHash)");
|
||||
}
|
||||
|
||||
var apiHash = _configuration["Telegram:ApiHash"]!.Trim();
|
||||
if (!TelegramApiConfigValidator.TryNormalizeApiHash(_configuration["Telegram:ApiHash"], out var apiHash, out var apiHashReason))
|
||||
{
|
||||
return new LoginResult(false, null, $"全局 Telegram API 配置无效:{apiHashReason}");
|
||||
}
|
||||
|
||||
var sessionsPath = _configuration["Telegram:SessionsPath"] ?? "sessions";
|
||||
Directory.CreateDirectory(sessionsPath);
|
||||
var normalizedPhone = NormalizePhoneForLogin(phone);
|
||||
@@ -45,35 +49,102 @@ public class AccountService : IAccountService
|
||||
|
||||
_logger.LogInformation("Starting login for phone {Phone}", normalizedPhone);
|
||||
|
||||
var client = await _clientPool.GetOrCreateClientAsync(accountId, apiId, apiHash, sessionPath, sessionKey: apiHash, phoneNumber: normalizedPhone, userId: null);
|
||||
WTelegram.Client? client = null;
|
||||
|
||||
string result;
|
||||
try
|
||||
{
|
||||
result = await client.Login(normalizedPhone);
|
||||
}
|
||||
catch (Exception ex) when (LooksLikeSessionApiMismatchOrCorrupted(ex))
|
||||
{
|
||||
// session 与 ApiId/ApiHash 不匹配或损坏,备份后重新开始登录流程
|
||||
TryBackupCorruptedSessionIfExists(sessionPath);
|
||||
await _clientPool.RemoveClientAsync(accountId);
|
||||
client = await _clientPool.GetOrCreateClientAsync(
|
||||
accountId,
|
||||
apiId,
|
||||
apiHash,
|
||||
sessionPath,
|
||||
sessionKey: apiHash,
|
||||
phoneNumber: normalizedPhone,
|
||||
userId: null);
|
||||
|
||||
client = await _clientPool.GetOrCreateClientAsync(accountId, apiId, apiHash, sessionPath, sessionKey: apiHash, phoneNumber: normalizedPhone, userId: null);
|
||||
result = await client.Login(normalizedPhone);
|
||||
string result;
|
||||
try
|
||||
{
|
||||
result = await client.Login(normalizedPhone);
|
||||
}
|
||||
catch (Exception ex) when (LooksLikeSessionApiMismatchOrCorrupted(ex))
|
||||
{
|
||||
// session 与 ApiId/ApiHash 不匹配或损坏,备份后重新开始登录流程
|
||||
TryBackupCorruptedSessionIfExists(sessionPath);
|
||||
await _clientPool.RemoveClientAsync(accountId);
|
||||
|
||||
client = await _clientPool.GetOrCreateClientAsync(
|
||||
accountId,
|
||||
apiId,
|
||||
apiHash,
|
||||
sessionPath,
|
||||
sessionKey: apiHash,
|
||||
phoneNumber: normalizedPhone,
|
||||
userId: null);
|
||||
|
||||
result = await client.Login(normalizedPhone);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Login flow next step for {Phone}: {Step}", normalizedPhone, result);
|
||||
|
||||
var loginResult = result switch
|
||||
{
|
||||
"verification_code" => new LoginResult(false, "code", "请输入验证码"),
|
||||
"password" => new LoginResult(false, "password", "请输入两步验证密码"),
|
||||
"name" => new LoginResult(false, "signup", "需要注册新账号"),
|
||||
"email" => new LoginResult(false, "email", "该账号需要邮箱验证(请按提示填写邮箱并完成验证)"),
|
||||
"email_verification_code" => new LoginResult(false, "email_code", "请输入邮箱验证码"),
|
||||
_ when client.User != null => new LoginResult(true, null, "登录成功", MapToAccountInfo(accountId, client)),
|
||||
_ => new LoginResult(false, null, $"未知状态: {result}")
|
||||
};
|
||||
|
||||
if (!loginResult.Success && string.IsNullOrWhiteSpace(loginResult.NextStep))
|
||||
{
|
||||
try { await _clientPool.RemoveClientAsync(accountId); } catch { }
|
||||
}
|
||||
|
||||
return loginResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _clientPool.RemoveClientAsync(accountId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
var hint = BuildFriendlyStartLoginError(ex);
|
||||
_logger.LogWarning(ex, "StartLogin failed for phone {Phone} (accountId={AccountId}): {Hint}", normalizedPhone, accountId, hint);
|
||||
return new LoginResult(false, null, hint);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildFriendlyStartLoginError(Exception ex)
|
||||
{
|
||||
var msg = ex.Message ?? string.Empty;
|
||||
|
||||
if (ex is FormatException
|
||||
|| msg.Contains("hex", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("hexadecimal", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("十六进制", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("16进制", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "发送验证码失败:检测到 ApiHash 格式异常。请到【系统设置】重新填写 Telegram ApiHash(my.telegram.org 获取的 32 位十六进制字符串)。";
|
||||
}
|
||||
|
||||
_logger.LogInformation("Login flow next step for {Phone}: {Step}", normalizedPhone, result);
|
||||
|
||||
return result switch
|
||||
if (ex is IOException
|
||||
|| msg.Contains("being used by another process", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("used by another process", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("process cannot access", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("被另一进程使用", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("被另一个进程使用", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
"verification_code" => new LoginResult(false, "code", "请输入验证码"),
|
||||
"password" => new LoginResult(false, "password", "请输入两步验证密码"),
|
||||
"name" => new LoginResult(false, "signup", "需要注册新账号"),
|
||||
"email" => new LoginResult(false, "email", "该账号需要邮箱验证(请按提示填写邮箱并完成验证)"),
|
||||
"email_verification_code" => new LoginResult(false, "email_code", "请输入邮箱验证码"),
|
||||
_ when client.User != null => new LoginResult(true, null, "登录成功", MapToAccountInfo(accountId, client)),
|
||||
_ => new LoginResult(false, null, $"未知状态: {result}")
|
||||
};
|
||||
return "发送验证码失败:session 文件被占用。请稍后重试;若你部署了多个实例共享同一 sessions 目录,请改为单实例或为每个实例使用独立 sessions 目录。";
|
||||
}
|
||||
|
||||
return $"发送验证码失败:{ex.GetType().Name}: {ex.Message}";
|
||||
}
|
||||
|
||||
public async Task<LoginResult> ResendCodeAsync(int accountId)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace TelegramPanel.Core.Utils;
|
||||
|
||||
public static class TelegramApiConfigValidator
|
||||
{
|
||||
public static bool TryValidateApiIdApiHash(int apiId, string? apiHash, out string? reason)
|
||||
{
|
||||
if (apiId <= 0)
|
||||
{
|
||||
reason = "ApiId 无效(必须为正整数)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryNormalizeApiHash(apiHash, out _, out reason))
|
||||
return false;
|
||||
|
||||
reason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryNormalizeApiHash(string? apiHash, out string normalized, out string? reason)
|
||||
{
|
||||
normalized = (apiHash ?? string.Empty).Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
reason = "ApiHash 不能为空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalized.Length != 32)
|
||||
{
|
||||
reason = "ApiHash 格式错误:长度应为 32 位十六进制字符串(0-9a-f)";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < normalized.Length; i++)
|
||||
{
|
||||
var ch = normalized[i];
|
||||
var isHex =
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
(ch >= 'a' && ch <= 'f') ||
|
||||
(ch >= 'A' && ch <= 'F');
|
||||
|
||||
if (!isHex)
|
||||
{
|
||||
reason = "ApiHash 格式错误:必须为十六进制字符串(0-9a-f)";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
reason = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,11 @@
|
||||
}
|
||||
|
||||
Snackbar.Add(result.Message ?? "发送验证码失败", Severity.Error);
|
||||
if (accountId != 0)
|
||||
{
|
||||
try { await AccountService.ReleaseClientAsync(accountId); } catch { }
|
||||
accountId = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,6 +303,11 @@
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"操作失败:{ex.Message}", Severity.Error);
|
||||
if (currentStep == LoginStep.Phone && accountId != 0)
|
||||
{
|
||||
try { await AccountService.ReleaseClientAsync(accountId); } catch { }
|
||||
accountId = 0;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@inject NavigationManager Navigation
|
||||
@inject CloudMailClient CloudMail
|
||||
@using TelegramPanel.Web.Services
|
||||
@using TelegramPanel.Core.Utils
|
||||
|
||||
<PageTitle>系统设置 - Telegram Panel</PageTitle>
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
<MudTextField @bind-Value="apiId" Label="默认 API ID" Variant="Variant.Outlined"
|
||||
HelperText="从 https://my.telegram.org 获取" />
|
||||
<MudTextField @bind-Value="apiHash" Label="默认 API Hash" Variant="Variant.Outlined" Class="mt-3"
|
||||
HelperText="用于新账号登录和导入" />
|
||||
HelperText="用于新账号登录和导入(32 位十六进制字符串)" />
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true" Class="mt-3">
|
||||
<MudText Typo="Typo.body2"><strong>写入位置:</strong>@LocalConfigPath</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mt-1"><strong>文件存在:</strong>@(LocalConfigExists ? "是" : "否")</MudText>
|
||||
@@ -315,13 +316,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TelegramApiConfigValidator.TryNormalizeApiHash(apiHash, out var normalizedApiHash, out var apiHashReason))
|
||||
{
|
||||
Snackbar.Add($"默认 API Hash 无效:{apiHashReason}", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var localConfigPath = LocalConfigPath;
|
||||
var root = await LoadOrCreateLocalConfigAsync();
|
||||
var telegram = EnsureObject(root, "Telegram");
|
||||
telegram["ApiId"] = parsedApiId;
|
||||
telegram["ApiHash"] = apiHash.Trim();
|
||||
telegram["ApiHash"] = normalizedApiHash;
|
||||
await SaveLocalConfigAsync(root);
|
||||
|
||||
// 配置变更后清理客户端缓存,确保后续操作使用新 ApiId/ApiHash
|
||||
|
||||
Reference in new issue
Block a user