|
|
@@ -128,11 +128,11 @@ export class AccountService {
|
|
|
if (!account) {
|
|
|
throw new AppError('账号不存在', HTTP_STATUS.NOT_FOUND, ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (!account.cookieData) {
|
|
|
throw new AppError('账号没有 Cookie 数据', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 尝试解密 Cookie
|
|
|
let decryptedCookies: string;
|
|
|
try {
|
|
|
@@ -143,7 +143,7 @@ export class AccountService {
|
|
|
logger.warn(`[AccountService] Cookie 解密失败,使用原始数据,账号: ${account.accountName}`);
|
|
|
decryptedCookies = account.cookieData;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 验证 Cookie 格式
|
|
|
try {
|
|
|
const parsed = JSON.parse(decryptedCookies);
|
|
|
@@ -156,7 +156,7 @@ export class AccountService {
|
|
|
} catch {
|
|
|
logger.warn(`[AccountService] Cookie 不是 JSON 格式,可能是字符串格式`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return decryptedCookies;
|
|
|
}
|
|
|
|
|
|
@@ -170,11 +170,11 @@ export class AccountService {
|
|
|
};
|
|
|
}): Promise<PlatformAccountType> {
|
|
|
const platform = data.platform as PlatformType;
|
|
|
-
|
|
|
+
|
|
|
// 解密 Cookie(如果是加密的)
|
|
|
let cookieData = data.cookieData;
|
|
|
let decryptedCookies: string;
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 尝试解密(如果是通过浏览器登录获取的加密Cookie)
|
|
|
decryptedCookies = CookieManager.decrypt(cookieData);
|
|
|
@@ -182,14 +182,14 @@ export class AccountService {
|
|
|
// 如果解密失败,可能是直接粘贴的Cookie字符串
|
|
|
decryptedCookies = cookieData;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 检查客户端传入的 accountId 是否有效(不是纯时间戳)
|
|
|
const clientAccountId = data.accountInfo?.accountId;
|
|
|
const isValidClientAccountId = clientAccountId && !this.isTimestampBasedId(clientAccountId);
|
|
|
-
|
|
|
+
|
|
|
// 从 Cookie 中提取账号 ID(部分平台的 Cookie 包含真实用户 ID)
|
|
|
const accountIdFromCookie = this.extractAccountIdFromCookie(platform, decryptedCookies);
|
|
|
-
|
|
|
+
|
|
|
// 某些平台应优先使用 API 返回的真实 ID,而不是 Cookie 中的值
|
|
|
// - 抖音:使用抖音号(unique_id,如 Ethanfly9392),而不是 Cookie 中的 passport_uid
|
|
|
// - 小红书:使用小红书号(red_num),而不是 Cookie 中的 userid
|
|
|
@@ -197,10 +197,10 @@ export class AccountService {
|
|
|
// - 视频号/头条:使用 API 返回的真实账号 ID
|
|
|
const platformsPreferApiId: PlatformType[] = ['douyin', 'xiaohongshu', 'baijiahao', 'weixin_video', 'toutiao'];
|
|
|
const preferApiId = platformsPreferApiId.includes(platform);
|
|
|
-
|
|
|
+
|
|
|
// 确定最终的 accountId
|
|
|
let finalAccountId: string;
|
|
|
-
|
|
|
+
|
|
|
if (preferApiId && isValidClientAccountId) {
|
|
|
// 对于优先使用 API ID 的平台,先用客户端传入的有效 ID
|
|
|
finalAccountId = this.normalizeAccountId(platform, clientAccountId);
|
|
|
@@ -217,7 +217,7 @@ export class AccountService {
|
|
|
finalAccountId = `${platform}_${Date.now()}`;
|
|
|
logger.warn(`[addAccount] Using timestamp-based accountId as fallback: ${finalAccountId}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 使用传入的账号信息(来自浏览器登录会话),或使用默认值
|
|
|
const accountInfo = {
|
|
|
accountId: finalAccountId,
|
|
|
@@ -226,14 +226,14 @@ export class AccountService {
|
|
|
fansCount: data.accountInfo?.fansCount || 0,
|
|
|
worksCount: data.accountInfo?.worksCount || 0,
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
logger.info(`Adding account for ${platform}: ${accountInfo.accountId}, name: ${accountInfo.accountName}`);
|
|
|
|
|
|
// 检查是否已存在相同账号
|
|
|
const existing = await this.accountRepository.findOne({
|
|
|
where: { userId, platform, accountId: accountInfo.accountId },
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
if (existing) {
|
|
|
// 更新已存在的账号
|
|
|
await this.accountRepository.update(existing.id, {
|
|
|
@@ -246,15 +246,15 @@ export class AccountService {
|
|
|
groupId: data.groupId || existing.groupId,
|
|
|
proxyConfig: data.proxyConfig || existing.proxyConfig,
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
const updated = await this.accountRepository.findOne({ where: { id: existing.id } });
|
|
|
wsManager.sendToUser(userId, WS_EVENTS.ACCOUNT_UPDATED, { account: this.formatAccount(updated!) });
|
|
|
-
|
|
|
+
|
|
|
// 异步刷新账号信息(获取准确的粉丝数、作品数等)
|
|
|
this.refreshAccountAsync(userId, existing.id, platform).catch(err => {
|
|
|
logger.warn(`[addAccount] Background refresh failed for existing account ${existing.id}:`, err);
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
return this.formatAccount(updated!);
|
|
|
}
|
|
|
|
|
|
@@ -301,9 +301,9 @@ export class AccountService {
|
|
|
private async refreshAccountAsync(userId: number, accountId: number, platform: PlatformType): Promise<void> {
|
|
|
// 延迟 2 秒执行,等待前端处理完成
|
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
-
|
|
|
+
|
|
|
logger.info(`[addAccount] Starting background refresh for account ${accountId} (${platform})`);
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
await this.refreshAccount(userId, accountId);
|
|
|
logger.info(`[addAccount] Background refresh completed for account ${accountId}`);
|
|
|
@@ -440,17 +440,17 @@ export class AccountService {
|
|
|
// 但是如果 API 调用失败,作为备用方案仍会尝试 AI
|
|
|
const platformsSkipAI: PlatformType[] = ['douyin', 'xiaohongshu', 'baijiahao'];
|
|
|
const shouldUseAI = aiService.isAvailable() && !platformsSkipAI.includes(platform);
|
|
|
-
|
|
|
+
|
|
|
logger.info(`[refreshAccount] Platform: ${platform}, shouldUseAI: ${shouldUseAI}, aiAvailable: ${aiService.isAvailable()}`);
|
|
|
-
|
|
|
+
|
|
|
// ========== AI 辅助刷新(部分平台使用) ==========
|
|
|
if (shouldUseAI) {
|
|
|
try {
|
|
|
logger.info(`[AI Refresh] Starting AI-assisted refresh for account ${accountId} (${platform})`);
|
|
|
-
|
|
|
+
|
|
|
// 使用无头浏览器截图,然后 AI 分析
|
|
|
const aiResult = await this.refreshAccountWithAI(platform, cookieList, accountId);
|
|
|
-
|
|
|
+
|
|
|
if (aiResult.needReLogin) {
|
|
|
// AI 检测到需要重新登录
|
|
|
updateData.status = 'expired';
|
|
|
@@ -478,7 +478,7 @@ export class AccountService {
|
|
|
// AI 刷新失败,继续使用原有逻辑
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// ========== 原有逻辑(AI 失败时的备用方案) ==========
|
|
|
if (!aiRefreshSuccess) {
|
|
|
const cookieStatus = await headlessBrowserService.checkCookieStatus(platform, cookieList);
|
|
|
@@ -501,7 +501,7 @@ export class AccountService {
|
|
|
|
|
|
// 检查是否获取到有效信息(排除默认名称)
|
|
|
const defaultNames = [
|
|
|
- `${platform}账号`, '未知账号', '抖音账号', '小红书账号',
|
|
|
+ `${platform}账号`, '未知账号', '抖音账号', '小红书账号',
|
|
|
'快手账号', '视频号账号', 'B站账号', '头条账号', '百家号账号'
|
|
|
];
|
|
|
const isValidProfile = profile.accountName && !defaultNames.includes(profile.accountName);
|
|
|
@@ -509,7 +509,7 @@ export class AccountService {
|
|
|
if (isValidProfile) {
|
|
|
updateData.accountName = profile.accountName;
|
|
|
updateData.avatarUrl = profile.avatarUrl;
|
|
|
-
|
|
|
+
|
|
|
// 仅在粉丝数有效时更新(避免因获取失败导致的归零)
|
|
|
if (profile.fansCount !== undefined) {
|
|
|
// 如果新粉丝数为 0,但原粉丝数 > 0,可能是获取失败,记录警告并跳过更新
|
|
|
@@ -519,13 +519,13 @@ export class AccountService {
|
|
|
updateData.fansCount = profile.fansCount;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (profile.worksCount === 0 && (account.worksCount || 0) > 0) {
|
|
|
logger.warn(`[refreshAccount] Works count dropped to 0 for ${accountId} (was ${account.worksCount}). Ignoring potential fetch error.`);
|
|
|
} else {
|
|
|
updateData.worksCount = profile.worksCount;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果获取到了有效的 accountId(如抖音号),也更新它
|
|
|
// 这样可以修正之前使用错误 ID(如 Cookie 值)保存的账号
|
|
|
if (profile.accountId && !this.isTimestampBasedId(profile.accountId)) {
|
|
|
@@ -536,7 +536,7 @@ export class AccountService {
|
|
|
logger.info(`[refreshAccount] Updating accountId from ${account.accountId} to ${newAccountId}`);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
logger.info(`Refreshed account info for ${platform}: ${profile.accountName}, fans: ${profile.fansCount}, works: ${profile.worksCount}`);
|
|
|
} else {
|
|
|
// 获取的信息无效,但 Cookie 有效,保持 active 状态
|
|
|
@@ -545,7 +545,7 @@ export class AccountService {
|
|
|
} catch (infoError) {
|
|
|
// 获取账号信息失败,但 Cookie 检查已通过,保持 active 状态
|
|
|
logger.warn(`Failed to fetch account info for ${accountId}, but cookie is valid:`, infoError);
|
|
|
-
|
|
|
+
|
|
|
// 对于百家号,如果是获取信息失败,可能是分散认证问题,不需要立即标记为失败
|
|
|
if (platform === 'baijiahao') {
|
|
|
logger.info(`[baijiahao] Account info fetch failed for ${accountId}, but this might be due to distributed auth. Keeping status active.`);
|
|
|
@@ -609,32 +609,32 @@ export class AccountService {
|
|
|
}> {
|
|
|
// 使用无头浏览器访问平台后台并截图
|
|
|
const screenshot = await headlessBrowserService.capturePageScreenshot(platform, cookieList);
|
|
|
-
|
|
|
+
|
|
|
if (!screenshot) {
|
|
|
throw new Error('Failed to capture screenshot');
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 第一步:使用 AI 分析登录状态
|
|
|
const loginStatus = await aiService.analyzeLoginStatus(screenshot, platform);
|
|
|
-
|
|
|
+
|
|
|
logger.info(`[AI Refresh] Login status for account ${accountId}:`, {
|
|
|
isLoggedIn: loginStatus.isLoggedIn,
|
|
|
hasVerification: loginStatus.hasVerification,
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 如果 AI 检测到未登录或有验证码,说明需要重新登录
|
|
|
if (!loginStatus.isLoggedIn || loginStatus.hasVerification) {
|
|
|
return { needReLogin: true };
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 第二步:使用 AI 提取账号信息
|
|
|
const accountInfo = await aiService.extractAccountInfo(screenshot, platform);
|
|
|
-
|
|
|
+
|
|
|
logger.info(`[AI Refresh] Account info extraction for ${accountId}:`, {
|
|
|
found: accountInfo.found,
|
|
|
accountName: accountInfo.accountName,
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
if (accountInfo.found && accountInfo.accountName) {
|
|
|
return {
|
|
|
needReLogin: false,
|
|
|
@@ -645,7 +645,7 @@ export class AccountService {
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// AI 未能提取到账号信息,但登录状态正常
|
|
|
// 返回空结果,让原有逻辑处理
|
|
|
return { needReLogin: false };
|
|
|
@@ -701,7 +701,7 @@ export class AccountService {
|
|
|
// 更新账号状态
|
|
|
if (cookieStatus.needReLogin) {
|
|
|
await this.accountRepository.update(accountId, { status: 'expired' });
|
|
|
- wsManager.sendToUser(userId, WS_EVENTS.ACCOUNT_UPDATED, {
|
|
|
+ wsManager.sendToUser(userId, WS_EVENTS.ACCOUNT_UPDATED, {
|
|
|
account: { ...this.formatAccount(account), status: 'expired' }
|
|
|
});
|
|
|
return { isValid: false, needReLogin: true, uncertain: false };
|
|
|
@@ -782,9 +782,41 @@ export class AccountService {
|
|
|
};
|
|
|
}> {
|
|
|
try {
|
|
|
- // 将 cookie 字符串转换为 cookie 列表格式
|
|
|
- const cookieList = this.parseCookieString(cookieData, platform);
|
|
|
-
|
|
|
+ const domainMap: Record<string, string> = {
|
|
|
+ douyin: '.douyin.com',
|
|
|
+ kuaishou: '.kuaishou.com',
|
|
|
+ xiaohongshu: '.xiaohongshu.com',
|
|
|
+ weixin_video: '.qq.com',
|
|
|
+ bilibili: '.bilibili.com',
|
|
|
+ toutiao: '.toutiao.com',
|
|
|
+ baijiahao: '.baidu.com',
|
|
|
+ qie: '.qq.com',
|
|
|
+ dayuhao: '.alibaba.com',
|
|
|
+ };
|
|
|
+
|
|
|
+ let cookieList: { name: string; value: string; domain: string; path: string }[] = [];
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(cookieData) as Array<{
|
|
|
+ name?: string;
|
|
|
+ value?: string;
|
|
|
+ domain?: string;
|
|
|
+ path?: string;
|
|
|
+ }>;
|
|
|
+ if (Array.isArray(parsed) && parsed.length > 0) {
|
|
|
+ const domain = domainMap[platform] || `.${platform}.com`;
|
|
|
+ cookieList = parsed
|
|
|
+ .filter(c => c?.name && c?.value)
|
|
|
+ .map(c => ({
|
|
|
+ name: String(c.name),
|
|
|
+ value: String(c.value),
|
|
|
+ domain: c.domain ? String(c.domain) : domain,
|
|
|
+ path: c.path ? String(c.path) : '/',
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ cookieList = this.parseCookieString(cookieData, platform);
|
|
|
+ }
|
|
|
+
|
|
|
if (cookieList.length === 0) {
|
|
|
return { success: false, message: 'Cookie 格式无效' };
|
|
|
}
|
|
|
@@ -793,10 +825,10 @@ export class AccountService {
|
|
|
// 如果能成功获取到有效信息,说明登录是有效的
|
|
|
try {
|
|
|
const profile = await headlessBrowserService.fetchAccountInfo(platform, cookieList);
|
|
|
-
|
|
|
+
|
|
|
// 检查是否获取到有效信息(排除默认名称)
|
|
|
const defaultNames = [
|
|
|
- `${platform}账号`, '未知账号', '抖音账号', '小红书账号',
|
|
|
+ `${platform}账号`, '未知账号', '抖音账号', '小红书账号',
|
|
|
'快手账号', '视频号账号', 'B站账号', '头条账号', '百家号账号'
|
|
|
];
|
|
|
const isValidProfile = profile.accountName && !defaultNames.includes(profile.accountName);
|
|
|
@@ -814,16 +846,16 @@ export class AccountService {
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 未能获取有效信息,再验证 Cookie 是否有效
|
|
|
logger.info(`[verifyCookieAndGetInfo] Could not get valid profile for ${platform}, checking cookie validity...`);
|
|
|
} catch (infoError) {
|
|
|
logger.warn(`Failed to fetch account info for ${platform}:`, infoError);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 账号信息获取失败或无效,检查 Cookie 是否有效
|
|
|
const cookieStatus = await headlessBrowserService.checkCookieStatus(platform, cookieList);
|
|
|
-
|
|
|
+
|
|
|
if (cookieStatus.isValid) {
|
|
|
// Cookie 有效但未能获取账号信息,返回基本成功
|
|
|
return {
|
|
|
@@ -847,9 +879,9 @@ export class AccountService {
|
|
|
return { success: false, message: '无法验证登录状态,请稍后重试' };
|
|
|
} catch (error) {
|
|
|
logger.error(`Failed to verify cookie for ${platform}:`, error);
|
|
|
- return {
|
|
|
- success: false,
|
|
|
- message: error instanceof Error ? error.message : '验证失败'
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ message: error instanceof Error ? error.message : '验证失败'
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
@@ -857,10 +889,10 @@ export class AccountService {
|
|
|
/**
|
|
|
* 将 cookie 字符串解析为 cookie 列表
|
|
|
*/
|
|
|
- private parseCookieString(cookieString: string, platform: PlatformType): {
|
|
|
- name: string;
|
|
|
- value: string;
|
|
|
- domain: string;
|
|
|
+ private parseCookieString(cookieString: string, platform: PlatformType): {
|
|
|
+ name: string;
|
|
|
+ value: string;
|
|
|
+ domain: string;
|
|
|
path: string;
|
|
|
}[] {
|
|
|
// 获取平台对应的域名
|
|
|
@@ -875,23 +907,23 @@ export class AccountService {
|
|
|
qie: '.qq.com',
|
|
|
dayuhao: '.alibaba.com',
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
const domain = domainMap[platform] || `.${platform}.com`;
|
|
|
-
|
|
|
+
|
|
|
// 解析 "name=value; name2=value2" 格式的 cookie 字符串
|
|
|
const cookies: { name: string; value: string; domain: string; path: string }[] = [];
|
|
|
-
|
|
|
+
|
|
|
const pairs = cookieString.split(';');
|
|
|
for (const pair of pairs) {
|
|
|
const trimmed = pair.trim();
|
|
|
if (!trimmed) continue;
|
|
|
-
|
|
|
+
|
|
|
const eqIndex = trimmed.indexOf('=');
|
|
|
if (eqIndex === -1) continue;
|
|
|
-
|
|
|
+
|
|
|
const name = trimmed.substring(0, eqIndex).trim();
|
|
|
const value = trimmed.substring(eqIndex + 1).trim();
|
|
|
-
|
|
|
+
|
|
|
if (name && value) {
|
|
|
cookies.push({
|
|
|
name,
|
|
|
@@ -901,7 +933,7 @@ export class AccountService {
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return cookies;
|
|
|
}
|
|
|
|
|
|
@@ -951,12 +983,12 @@ export class AccountService {
|
|
|
qie: ['uin', 'skey', 'p_uin'],
|
|
|
dayuhao: ['login_aliyunid', 'cna', 'munb'],
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
const targetCookieNames = platformCookieNames[platform] || [];
|
|
|
if (targetCookieNames.length === 0) {
|
|
|
return null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 尝试解析 JSON 格式的 Cookie
|
|
|
let cookieList: { name: string; value: string }[];
|
|
|
@@ -969,18 +1001,18 @@ export class AccountService {
|
|
|
value: c.value,
|
|
|
}));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (!Array.isArray(cookieList) || cookieList.length === 0) {
|
|
|
return null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 按优先级查找 Cookie
|
|
|
for (const cookieName of targetCookieNames) {
|
|
|
const cookie = cookieList.find(c => c.name === cookieName);
|
|
|
if (cookie?.value) {
|
|
|
// 获取 Cookie 值,处理可能的编码
|
|
|
let cookieValue = cookie.value;
|
|
|
-
|
|
|
+
|
|
|
// 处理特殊格式的 Cookie(如 ttwid 可能包含分隔符)
|
|
|
if (cookieValue.includes('|')) {
|
|
|
cookieValue = cookieValue.split('|')[1] || cookieValue;
|
|
|
@@ -992,18 +1024,18 @@ export class AccountService {
|
|
|
// 解码失败,使用原值
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 截取合理长度(避免过长的 ID)
|
|
|
if (cookieValue.length > 64) {
|
|
|
cookieValue = cookieValue.slice(0, 64);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const accountId = `${platform}_${cookieValue}`;
|
|
|
logger.info(`[extractAccountIdFromCookie] Found ${cookieName} for ${platform}: ${accountId}`);
|
|
|
return accountId;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return null;
|
|
|
} catch (error) {
|
|
|
logger.warn(`[extractAccountIdFromCookie] Failed to extract accountId from cookie for ${platform}:`, error);
|
|
|
@@ -1032,21 +1064,21 @@ export class AccountService {
|
|
|
*/
|
|
|
private normalizeAccountId(platform: PlatformType, accountId: string): string {
|
|
|
const shortPrefix = AccountService.SHORT_PREFIX_MAP[platform] || `${platform}_`;
|
|
|
-
|
|
|
+
|
|
|
if (!accountId) {
|
|
|
return `${shortPrefix}${Date.now()}`;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果已经有正确的短前缀,直接返回
|
|
|
if (accountId.startsWith(shortPrefix)) {
|
|
|
return accountId;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 移除任何已有的前缀(短前缀或完整前缀)
|
|
|
const allShortPrefixes = Object.values(AccountService.SHORT_PREFIX_MAP);
|
|
|
const allFullPrefixes = Object.keys(AccountService.SHORT_PREFIX_MAP).map(p => `${p}_`);
|
|
|
const allPrefixes = [...allShortPrefixes, ...allFullPrefixes];
|
|
|
-
|
|
|
+
|
|
|
let cleanId = accountId;
|
|
|
for (const prefix of allPrefixes) {
|
|
|
if (cleanId.startsWith(prefix)) {
|
|
|
@@ -1054,7 +1086,7 @@ export class AccountService {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 添加正确的短前缀
|
|
|
return `${shortPrefix}${cleanId}`;
|
|
|
}
|
|
|
@@ -1065,26 +1097,26 @@ export class AccountService {
|
|
|
*/
|
|
|
private isTimestampBasedId(accountId: string): boolean {
|
|
|
if (!accountId) return true;
|
|
|
-
|
|
|
+
|
|
|
// 检查是否匹配 前缀_时间戳 格式(支持短前缀和完整前缀)
|
|
|
const timestampPattern = /^[a-z_]+_(\d{13,})$/;
|
|
|
const match = accountId.match(timestampPattern);
|
|
|
if (!match) {
|
|
|
return false;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 提取数字部分,检查是否是合理的时间戳(2020年到2030年之间)
|
|
|
if (match[1]) {
|
|
|
const timestamp = parseInt(match[1]);
|
|
|
const minTimestamp = new Date('2020-01-01').getTime(); // 1577836800000
|
|
|
const maxTimestamp = new Date('2030-01-01').getTime(); // 1893456000000
|
|
|
-
|
|
|
+
|
|
|
if (timestamp >= minTimestamp && timestamp <= maxTimestamp) {
|
|
|
logger.info(`[isTimestampBasedId] Detected timestamp-based ID: ${accountId}`);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return false;
|
|
|
}
|
|
|
}
|