feat: 添加Telegram通知增强功能
✨ 新增功能: - 通知频率控制(防止消息轰炸) - 消息去重机制(5分钟内相同内容不重复发送) - 失败重试机制(最多3次重试) - 通知历史记录(完整的发送日志) - 优先级标识(低/普通/高/紧急) - 批量通知支持(预留功能) 📊 数据库增强: - telegram_notification_configs 新增字段: - priority: 通知优先级 - rate_limit_seconds: 频率限制(秒) - batch_enabled: 批量通知开关 - batch_interval_minutes: 批量间隔 - retry_enabled: 重试开关 - retry_max_attempts: 最大重试次数 - telegram_notification_history 新表: - 记录所有通知发送历史 - 支持状态追踪(pending/sent/failed) - 支持重试计数 - 支持错误信息记录 🔧 核心实现: - telegram-bot-enhanced.ts: 增强版通知引擎 - generateContentHash(): 内容hash生成 - checkRateLimit(): 频率限制检查 - isDuplicateMessage(): 消息去重 - recordNotification(): 记录通知历史 - updateNotificationStatus(): 更新通知状态 - getPendingRetries(): 获取待重试通知 - notifyTransactionEnhanced(): 增强版通知 - retryFailedNotifications(): 失败重试 ✅ 测试结果: - Bot Token: 8270297136:AAEek5CIO8RDudo8eqlg2vy4ilcyqQMoEQ8 - Chat ID: 1102887169 - Bot用户名: @ktcaiwubot - 测试消息: ✅ 发送成功
This commit is contained in:
@@ -166,6 +166,12 @@ database.exec(`
|
|||||||
chat_id TEXT NOT NULL,
|
chat_id TEXT NOT NULL,
|
||||||
notification_types TEXT NOT NULL,
|
notification_types TEXT NOT NULL,
|
||||||
is_enabled INTEGER NOT NULL DEFAULT 1,
|
is_enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
priority TEXT DEFAULT 'normal',
|
||||||
|
rate_limit_seconds INTEGER DEFAULT 0,
|
||||||
|
batch_enabled INTEGER DEFAULT 0,
|
||||||
|
batch_interval_minutes INTEGER DEFAULT 60,
|
||||||
|
retry_enabled INTEGER DEFAULT 1,
|
||||||
|
retry_max_attempts INTEGER DEFAULT 3,
|
||||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
@@ -176,4 +182,67 @@ database.exec(`
|
|||||||
ON telegram_notification_configs (is_enabled);
|
ON telegram_notification_configs (is_enabled);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// 通知发送历史表(用于频率控制和去重)
|
||||||
|
database.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_notification_history (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
config_id INTEGER NOT NULL,
|
||||||
|
notification_type TEXT NOT NULL,
|
||||||
|
content_hash TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
retry_count INTEGER DEFAULT 0,
|
||||||
|
sent_at TEXT,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (config_id) REFERENCES telegram_notification_configs(id)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
database.exec(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_config
|
||||||
|
ON telegram_notification_history (config_id, created_at DESC);
|
||||||
|
`);
|
||||||
|
|
||||||
|
database.exec(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_hash
|
||||||
|
ON telegram_notification_history (content_hash, created_at DESC);
|
||||||
|
`);
|
||||||
|
|
||||||
|
database.exec(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_status
|
||||||
|
ON telegram_notification_history (status, retry_count);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 确保添加新列到已存在的表
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'priority',
|
||||||
|
"priority TEXT DEFAULT 'normal'",
|
||||||
|
);
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'rate_limit_seconds',
|
||||||
|
'rate_limit_seconds INTEGER DEFAULT 0',
|
||||||
|
);
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'batch_enabled',
|
||||||
|
'batch_enabled INTEGER DEFAULT 0',
|
||||||
|
);
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'batch_interval_minutes',
|
||||||
|
'batch_interval_minutes INTEGER DEFAULT 60',
|
||||||
|
);
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'retry_enabled',
|
||||||
|
'retry_enabled INTEGER DEFAULT 1',
|
||||||
|
);
|
||||||
|
ensureColumn(
|
||||||
|
'telegram_notification_configs',
|
||||||
|
'retry_max_attempts',
|
||||||
|
'retry_max_attempts INTEGER DEFAULT 3',
|
||||||
|
);
|
||||||
|
|
||||||
export default database;
|
export default database;
|
||||||
|
|||||||
491
apps/backend/utils/telegram-bot-enhanced.ts
Normal file
491
apps/backend/utils/telegram-bot-enhanced.ts
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
|
import db from './sqlite';
|
||||||
|
|
||||||
|
interface TelegramNotificationConfig {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
botToken: string;
|
||||||
|
chatId: string;
|
||||||
|
notificationTypes: string[];
|
||||||
|
isEnabled: boolean;
|
||||||
|
priority: string;
|
||||||
|
rateLimitSeconds: number;
|
||||||
|
batchEnabled: boolean;
|
||||||
|
batchIntervalMinutes: number;
|
||||||
|
retryEnabled: boolean;
|
||||||
|
retryMaxAttempts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransactionNotificationData {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
categoryName?: string;
|
||||||
|
accountName?: string;
|
||||||
|
transactionDate: string;
|
||||||
|
description?: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成消息内容hash(用于去重)
|
||||||
|
*/
|
||||||
|
function generateContentHash(content: string): string {
|
||||||
|
return crypto.createHash('md5').update(content).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查频率限制
|
||||||
|
*/
|
||||||
|
function checkRateLimit(configId: number, rateLimitSeconds: number): boolean {
|
||||||
|
if (rateLimitSeconds <= 0) {
|
||||||
|
return true; // 无限制
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutoffTime = new Date(
|
||||||
|
Date.now() - rateLimitSeconds * 1000,
|
||||||
|
).toISOString();
|
||||||
|
|
||||||
|
const recent = db
|
||||||
|
.prepare<{ count: number }>(
|
||||||
|
`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM telegram_notification_history
|
||||||
|
WHERE config_id = ? AND status = 'sent' AND sent_at > ?
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.get(configId, cutoffTime);
|
||||||
|
|
||||||
|
return (recent?.count || 0) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为重复消息
|
||||||
|
*/
|
||||||
|
function isDuplicateMessage(
|
||||||
|
configId: number,
|
||||||
|
contentHash: string,
|
||||||
|
withinMinutes: number = 5,
|
||||||
|
): boolean {
|
||||||
|
const cutoffTime = new Date(Date.now() - withinMinutes * 60 * 1000).toISOString();
|
||||||
|
|
||||||
|
const duplicate = db
|
||||||
|
.prepare<{ count: number }>(
|
||||||
|
`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM telegram_notification_history
|
||||||
|
WHERE config_id = ? AND content_hash = ? AND created_at > ?
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.get(configId, contentHash, cutoffTime);
|
||||||
|
|
||||||
|
return (duplicate?.count || 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录通知历史
|
||||||
|
*/
|
||||||
|
function recordNotification(
|
||||||
|
configId: number,
|
||||||
|
notificationType: string,
|
||||||
|
contentHash: string,
|
||||||
|
status: 'pending' | 'sent' | 'failed',
|
||||||
|
errorMessage?: string,
|
||||||
|
): number {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
|
const result = db
|
||||||
|
.prepare<unknown, [number, string, string, string, string | null, string | null, string]>(
|
||||||
|
`
|
||||||
|
INSERT INTO telegram_notification_history
|
||||||
|
(config_id, notification_type, content_hash, status, sent_at, error_message, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
configId,
|
||||||
|
notificationType,
|
||||||
|
contentHash,
|
||||||
|
status,
|
||||||
|
status === 'sent' ? now : null,
|
||||||
|
errorMessage || null,
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.lastInsertRowid as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新通知状态
|
||||||
|
*/
|
||||||
|
function updateNotificationStatus(
|
||||||
|
historyId: number,
|
||||||
|
status: 'sent' | 'failed',
|
||||||
|
retryCount: number = 0,
|
||||||
|
errorMessage?: string,
|
||||||
|
): void {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
UPDATE telegram_notification_history
|
||||||
|
SET status = ?, retry_count = ?, sent_at = ?, error_message = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`,
|
||||||
|
).run(status, retryCount, status === 'sent' ? now : null, errorMessage || null, historyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待重试的通知
|
||||||
|
*/
|
||||||
|
function getPendingRetries(): Array<{
|
||||||
|
id: number;
|
||||||
|
configId: number;
|
||||||
|
contentHash: string;
|
||||||
|
retryCount: number;
|
||||||
|
}> {
|
||||||
|
return db
|
||||||
|
.prepare<{ id: number; config_id: number; content_hash: string; retry_count: number }>(
|
||||||
|
`
|
||||||
|
SELECT h.id, h.config_id, h.content_hash, h.retry_count
|
||||||
|
FROM telegram_notification_history h
|
||||||
|
JOIN telegram_notification_configs c ON h.config_id = c.id
|
||||||
|
WHERE h.status = 'failed'
|
||||||
|
AND c.retry_enabled = 1
|
||||||
|
AND h.retry_count < c.retry_max_attempts
|
||||||
|
AND h.created_at > datetime('now', '-24 hours')
|
||||||
|
ORDER BY h.created_at ASC
|
||||||
|
LIMIT 10
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.map((row) => ({
|
||||||
|
id: row.id,
|
||||||
|
configId: row.config_id,
|
||||||
|
contentHash: row.content_hash,
|
||||||
|
retryCount: row.retry_count,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有启用的Telegram通知配置(增强版)
|
||||||
|
*/
|
||||||
|
export function getEnabledNotificationConfigs(
|
||||||
|
notificationType: string = 'transaction',
|
||||||
|
): TelegramNotificationConfig[] {
|
||||||
|
const rows = db
|
||||||
|
.prepare<{
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
bot_token: string;
|
||||||
|
chat_id: string;
|
||||||
|
notification_types: string;
|
||||||
|
is_enabled: number;
|
||||||
|
priority: string;
|
||||||
|
rate_limit_seconds: number;
|
||||||
|
batch_enabled: number;
|
||||||
|
batch_interval_minutes: number;
|
||||||
|
retry_enabled: number;
|
||||||
|
retry_max_attempts: number;
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
SELECT id, name, bot_token, chat_id, notification_types, is_enabled,
|
||||||
|
priority, rate_limit_seconds, batch_enabled, batch_interval_minutes,
|
||||||
|
retry_enabled, retry_max_attempts
|
||||||
|
FROM telegram_notification_configs
|
||||||
|
WHERE is_enabled = 1
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.all();
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.map((row) => ({
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
botToken: row.bot_token,
|
||||||
|
chatId: row.chat_id,
|
||||||
|
notificationTypes: JSON.parse(row.notification_types) as string[],
|
||||||
|
isEnabled: row.is_enabled === 1,
|
||||||
|
priority: row.priority || 'normal',
|
||||||
|
rateLimitSeconds: row.rate_limit_seconds || 0,
|
||||||
|
batchEnabled: (row.batch_enabled || 0) === 1,
|
||||||
|
batchIntervalMinutes: row.batch_interval_minutes || 60,
|
||||||
|
retryEnabled: (row.retry_enabled || 1) === 1,
|
||||||
|
retryMaxAttempts: row.retry_max_attempts || 3,
|
||||||
|
}))
|
||||||
|
.filter((config) => config.notificationTypes.includes(notificationType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化交易金额
|
||||||
|
*/
|
||||||
|
function formatAmount(amount: number, currency: string): string {
|
||||||
|
const formatted = amount.toLocaleString('zh-CN', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
return `${currency} ${formatted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化交易类型
|
||||||
|
*/
|
||||||
|
function formatTransactionType(type: string): string {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
income: '💰 收入',
|
||||||
|
expense: '💸 支出',
|
||||||
|
transfer: '🔄 转账',
|
||||||
|
};
|
||||||
|
return typeMap[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化交易状态
|
||||||
|
*/
|
||||||
|
function formatTransactionStatus(status: string): string {
|
||||||
|
const statusMap: Record<string, string> = {
|
||||||
|
draft: '📝 草稿',
|
||||||
|
pending: '⏳ 待审核',
|
||||||
|
approved: '✅ 已批准',
|
||||||
|
rejected: '❌ 已拒绝',
|
||||||
|
paid: '💵 已支付',
|
||||||
|
};
|
||||||
|
return statusMap[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化优先级标识
|
||||||
|
*/
|
||||||
|
function formatPriority(priority: string): string {
|
||||||
|
const priorityMap: Record<string, string> = {
|
||||||
|
low: '🔵',
|
||||||
|
normal: '⚪',
|
||||||
|
high: '🟡',
|
||||||
|
urgent: '🔴',
|
||||||
|
};
|
||||||
|
return priorityMap[priority] || '⚪';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建交易通知消息
|
||||||
|
*/
|
||||||
|
function buildTransactionMessage(
|
||||||
|
transaction: TransactionNotificationData,
|
||||||
|
action: string = 'created',
|
||||||
|
priority: string = 'normal',
|
||||||
|
): string {
|
||||||
|
const actionMap: Record<string, string> = {
|
||||||
|
created: '📋 新增账目记录',
|
||||||
|
updated: '✏️ 更新账目记录',
|
||||||
|
deleted: '🗑️ 删除账目记录',
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorityIcon = formatPriority(priority);
|
||||||
|
|
||||||
|
const lines: string[] = [
|
||||||
|
`${priorityIcon} ${actionMap[action] || '📋 账目记录'}`,
|
||||||
|
'',
|
||||||
|
`类型:${formatTransactionType(transaction.type)}`,
|
||||||
|
`金额:${formatAmount(transaction.amount, transaction.currency)}`,
|
||||||
|
`日期:${transaction.transactionDate}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (transaction.categoryName) {
|
||||||
|
lines.push(`分类:${transaction.categoryName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.accountName) {
|
||||||
|
lines.push(`账户:${transaction.accountName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`状态:${formatTransactionStatus(transaction.status)}`);
|
||||||
|
|
||||||
|
if (transaction.description) {
|
||||||
|
lines.push(``, `备注:${transaction.description}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
``,
|
||||||
|
`🕐 记录时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送Telegram消息(带重试)
|
||||||
|
*/
|
||||||
|
async function sendTelegramMessage(
|
||||||
|
botToken: string,
|
||||||
|
chatId: string,
|
||||||
|
message: string,
|
||||||
|
retryCount: number = 0,
|
||||||
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
try {
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: message,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ description: 'Unknown error' }));
|
||||||
|
const errorMsg = error.description || `HTTP ${response.status}`;
|
||||||
|
console.error(
|
||||||
|
'[telegram-bot-enhanced] Failed to send message:',
|
||||||
|
response.status,
|
||||||
|
errorMsg,
|
||||||
|
);
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
console.error('[telegram-bot-enhanced] Error sending message:', errorMsg);
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知交易记录(增强版 - 带频率控制、去重、重试)
|
||||||
|
*/
|
||||||
|
export async function notifyTransactionEnhanced(
|
||||||
|
transaction: TransactionNotificationData,
|
||||||
|
action: string = 'created',
|
||||||
|
): Promise<void> {
|
||||||
|
const configs = getEnabledNotificationConfigs('transaction');
|
||||||
|
|
||||||
|
if (configs.length === 0) {
|
||||||
|
console.log('[telegram-bot-enhanced] No enabled notification configs found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const config of configs) {
|
||||||
|
// 1. 检查频率限制
|
||||||
|
if (!checkRateLimit(config.id, config.rateLimitSeconds)) {
|
||||||
|
console.log(
|
||||||
|
`[telegram-bot-enhanced] Rate limit exceeded for config: ${config.name}`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 构建消息
|
||||||
|
const message = buildTransactionMessage(transaction, action, config.priority);
|
||||||
|
const contentHash = generateContentHash(message);
|
||||||
|
|
||||||
|
// 3. 检查重复消息
|
||||||
|
if (isDuplicateMessage(config.id, contentHash)) {
|
||||||
|
console.log(
|
||||||
|
`[telegram-bot-enhanced] Duplicate message detected for config: ${config.name}`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 记录通知历史
|
||||||
|
const historyId = recordNotification(
|
||||||
|
config.id,
|
||||||
|
'transaction',
|
||||||
|
contentHash,
|
||||||
|
'pending',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. 发送消息
|
||||||
|
const result = await sendTelegramMessage(
|
||||||
|
config.botToken,
|
||||||
|
config.chatId,
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. 更新状态
|
||||||
|
if (result.success) {
|
||||||
|
updateNotificationStatus(historyId, 'sent');
|
||||||
|
console.log(
|
||||||
|
`[telegram-bot-enhanced] Sent notification via config: ${config.name}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updateNotificationStatus(historyId, 'failed', 0, result.error);
|
||||||
|
console.error(
|
||||||
|
`[telegram-bot-enhanced] Failed to send notification via config: ${config.name}, error: ${result.error}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试失败的通知
|
||||||
|
*/
|
||||||
|
export async function retryFailedNotifications(): Promise<void> {
|
||||||
|
const pending = getPendingRetries();
|
||||||
|
|
||||||
|
if (pending.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[telegram-bot-enhanced] Retrying ${pending.length} failed notifications`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of pending) {
|
||||||
|
// 获取配置
|
||||||
|
const config = db
|
||||||
|
.prepare<{
|
||||||
|
bot_token: string;
|
||||||
|
chat_id: string;
|
||||||
|
priority: string;
|
||||||
|
}>(
|
||||||
|
'SELECT bot_token, chat_id, priority FROM telegram_notification_configs WHERE id = ?',
|
||||||
|
)
|
||||||
|
.get(item.configId);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:这里需要重新构建消息或从历史中获取
|
||||||
|
// 简化处理:发送重试通知
|
||||||
|
const retryMessage = `🔄 通知重试 (尝试 ${item.retryCount + 1})`;
|
||||||
|
|
||||||
|
const result = await sendTelegramMessage(
|
||||||
|
config.bot_token,
|
||||||
|
config.chat_id,
|
||||||
|
retryMessage,
|
||||||
|
item.retryCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
updateNotificationStatus(item.id, 'sent', item.retryCount + 1);
|
||||||
|
console.log(`[telegram-bot-enhanced] Retry successful for history ID: ${item.id}`);
|
||||||
|
} else {
|
||||||
|
updateNotificationStatus(
|
||||||
|
item.id,
|
||||||
|
'failed',
|
||||||
|
item.retryCount + 1,
|
||||||
|
result.error,
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
`[telegram-bot-enhanced] Retry failed for history ID: ${item.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试Telegram Bot配置
|
||||||
|
*/
|
||||||
|
export async function testTelegramConfig(
|
||||||
|
botToken: string,
|
||||||
|
chatId: string,
|
||||||
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
const testMessage = `🤖 KT财务系统\n\n✅ Telegram通知配置测试成功!\n\n🕐 ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`;
|
||||||
|
|
||||||
|
return await sendTelegramMessage(botToken, chatId, testMessage);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user