issue-ai/src/lib/monitor/wechat-pusher.ts

77 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/lib/monitor/wechat-pusher.ts
import type { MonitorConfig } from './types'
import { formatBeijingTime } from './types'
const logger = {
info: (msg: string) => console.log(`[Worker] ${formatBeijingTime()} INFO ${msg}`),
error: (msg: string) => console.error(`[Worker] ${formatBeijingTime()} ERROR ${msg}`),
}
export class WeChatPusher {
async pushText(text: string, webhookUrl: string): Promise<boolean> {
if (!webhookUrl) { logger.error('Webhook URL not configured'); return false }
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ msgtype: 'text', text: { content: text } }),
signal: AbortSignal.timeout(5_000),
})
if (!response.ok) { logger.error(`Webhook HTTP ${response.status}`); return false }
const result = await response.json() as { errcode?: number; errmsg?: string }
if (result.errcode === 0) { logger.info('WeChat message sent'); return true }
logger.error(`WeChat error: ${result.errmsg}`)
return false
} catch (e) {
logger.error(`Webhook failed: ${e instanceof Error ? e.message : e}`)
return false
}
}
formatAvailabilityMessage(
deadlines: Record<string, { deadline: string; hours: number }>,
faultInfo: { server_ip: string | null; server_sn: string | null; fault_time: string | null },
isOemDiag: boolean,
oemDeadline: string | null,
orderNumber: string | null,
rackPosition: string | null,
faultDetail: string | null,
): string {
let msg = ''
const typeLabel = isOemDiag ? 'OEM诊断' : 'OEM维修'
msg += `【服务器故障单】 - ${typeLabel}\n\n`
if (orderNumber) msg += `工单号:${orderNumber}\n`
msg += `服务器IP${faultInfo.server_ip || '未知'}\n`
msg += `服务器SN${faultInfo.server_sn || '未知'}\n`
msg += `机架位置:${rackPosition || '未知'}\n`
if (faultInfo.fault_time) msg += `故障时间:${faultInfo.fault_time}\n`
if (faultDetail) msg += `故障信息:${faultDetail}\n`
if (isOemDiag && oemDeadline) {
msg += `到期时间:${oemDeadline}\n`
} else if (!isOemDiag) {
msg += '\n📊 服务可用性各档位截止时间:\n\n'
const tierDesc: Record<string, string> = {
'第一档': '99%(含)以上(不扣费)',
'第二档': '99%-97%扣10%',
'第三档': '97%-95%扣25%',
'第四档': '95%-90%扣50%',
'第五档': '90%不含以下扣100%',
}
const emojis = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣']
let i = 0
for (const [tier, desc] of Object.entries(tierDesc)) {
const d = deadlines[tier]
if (d) {
msg += `${emojis[i]} ${desc}\n`
msg += ` 截止时间:${d.deadline}`
if (d.hours > 0) msg += `${d.hours}小时)`
msg += '\n\n'
}
i++
}
}
return msg.trim()
}
}