292 lines
10 KiB
JavaScript
292 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 企业微信消息推送模拟测试
|
||
* 模拟 29.237.253.1 服务器 OEM 诊断和 OEM 维修故障完整生命周期
|
||
* 每条消息间隔 5 秒
|
||
*/
|
||
|
||
const WEBHOOK_URL = process.env.WECOM_WEBHOOK_URL || (() => { throw new Error('WECOM_WEBHOOK_URL not set') })()
|
||
const SERVER_IP = '29.237.253.1'
|
||
const SERVER_SN = '210235A4M7H241000098'
|
||
const RACK = 'D01-404-I02-3U'
|
||
const INTERVAL = 5000
|
||
|
||
// 随机故障内容
|
||
const OEM_DIAG_FAULTS = [
|
||
'agent已经5分钟没有上报数据',
|
||
'GPU温度传感器读数异常,超过阈值',
|
||
'系统日志显示内核恐慌(Kernel Panic)',
|
||
'NVMe硬盘SMART检测到坏块',
|
||
'内存ECC错误计数超标',
|
||
]
|
||
const OEM_REPAIR_FAULTS = [
|
||
'GPU缓存发生不可纠正错误',
|
||
'RAID阵列降级,一块硬盘离线',
|
||
'电源模块冗余失效,单电源运行',
|
||
'主板BMC固件异常,无法远程管理',
|
||
'网卡绑定故障,网络中断',
|
||
]
|
||
|
||
const randomFrom = (arr) => arr[Math.floor(Math.random() * arr.length)]
|
||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||
|
||
// 北京时间格式化
|
||
function bjTime(date = new Date()) {
|
||
const d = new Date(date.getTime() + 8 * 3600_000)
|
||
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')} ${String(d.getUTCHours()).padStart(2, '0')}:${String(d.getUTCMinutes()).padStart(2, '0')}:${String(d.getUTCSeconds()).padStart(2, '0')}`
|
||
}
|
||
|
||
// 向前推算时间
|
||
function minusHours(date, hours) {
|
||
return new Date(date.getTime() - hours * 3600_000)
|
||
}
|
||
function minusMinutes(date, minutes) {
|
||
return new Date(date.getTime() - minutes * 60_000)
|
||
}
|
||
|
||
async function sendText(text) {
|
||
const res = await fetch(WEBHOOK_URL, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ msgtype: 'text', text: { content: text } }),
|
||
})
|
||
const data = await res.json()
|
||
return data.errcode === 0
|
||
}
|
||
|
||
async function send(label, text) {
|
||
console.log(`\n📤 [${label}]`)
|
||
console.log(text)
|
||
console.log('─'.repeat(50))
|
||
const ok = await sendText(text)
|
||
console.log(ok ? '✅ 发送成功' : '❌ 发送失败')
|
||
await sleep(INTERVAL)
|
||
}
|
||
|
||
// ============================================================
|
||
// OEM 诊断完整生命周期
|
||
// ============================================================
|
||
async function runOemDiag() {
|
||
const now = new Date()
|
||
const faultTime = minusHours(now, 2)
|
||
const orderNum = 'S' + Date.now().toString().slice(-10)
|
||
const fault = randomFrom(OEM_DIAG_FAULTS)
|
||
const oemDeadline = minusHours(faultTime, -6) // 故障后6小时到期
|
||
|
||
console.log('\n' + '═'.repeat(60))
|
||
console.log(' OEM 诊断 — 完整生命周期模拟')
|
||
console.log('═'.repeat(60))
|
||
console.log(`工单号: ${orderNum}`)
|
||
console.log(`故障时间: ${bjTime(faultTime)}`)
|
||
console.log(`到期时间: ${bjTime(oemDeadline)}`)
|
||
|
||
// 1. 初始故障通知
|
||
await send('初始通知', [
|
||
`【服务器故障单】 - OEM诊断`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`故障时间:${bjTime(faultTime)}`,
|
||
`到期时间:${bjTime(oemDeadline)}`,
|
||
`故障信息:${fault}`,
|
||
].join('\n'))
|
||
|
||
// 2. 每小时提醒(剩余4小时)
|
||
const t4h = minusMinutes(oemDeadline, 240)
|
||
await send('每小时提醒(剩余4小时0分)', [
|
||
`⏰ OEM诊断工单提醒(剩余4小时0分)`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`到期时间:${bjTime(oemDeadline)}`,
|
||
'',
|
||
`服务器尚未录入恢复时间,请及时处理!`,
|
||
].join('\n'))
|
||
|
||
// 3. 30分钟到期提醒
|
||
await send('30分钟到期提醒', [
|
||
`⚠️ OEM诊断工单即将到期(剩余30分钟)`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`到期时间:${bjTime(oemDeadline)}`,
|
||
'',
|
||
`服务器尚未录入恢复时间,请尽快处理!`,
|
||
].join('\n'))
|
||
|
||
// 4. 超时提醒
|
||
await send('超时提醒', [
|
||
`⚠️ OEM诊断工单已超时`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`到期时间:${bjTime(oemDeadline)}`,
|
||
'',
|
||
`服务器尚未录入恢复时间,请尽快处理!`,
|
||
].join('\n'))
|
||
|
||
// 5. 结单消息
|
||
const closeTime = minusMinutes(now, -30)
|
||
const durationMin = Math.floor((closeTime.getTime() - faultTime.getTime()) / 60000)
|
||
const durH = Math.floor(durationMin / 60)
|
||
const durM = durationMin % 60
|
||
await send('结单消息', [
|
||
`✅ ${orderNum}工单已结单`,
|
||
'',
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`故障类型:OEM诊断`,
|
||
`故障时间:${bjTime(faultTime)}`,
|
||
`结单时间:${bjTime(closeTime)}`,
|
||
`本次处理时长:${durH}小时${durM}分钟`,
|
||
].join('\n'))
|
||
}
|
||
|
||
// ============================================================
|
||
// OEM 维修完整生命周期
|
||
// ============================================================
|
||
async function runOemRepair() {
|
||
const now = new Date()
|
||
const faultTime = minusHours(now, 4)
|
||
const orderNum = 'S' + Date.now().toString().slice(-10)
|
||
const fault = randomFrom(OEM_REPAIR_FAULTS)
|
||
|
||
// 五档截止时间
|
||
const tiers = [
|
||
{ name: '第一档', desc: '99%(含)以上(不扣费)', hours: 8, penalty: '10%' },
|
||
{ name: '第二档', desc: '99%-97%(含)(扣10%)', hours: 24, penalty: '25%' },
|
||
{ name: '第三档', desc: '97%-95%(含)(扣25%)', hours: 48, penalty: '50%' },
|
||
{ name: '第四档', desc: '95%-90%(含)(扣50%)', hours: 72, penalty: '100%' },
|
||
{ name: '第五档', desc: '90%(不含)以下(扣100%)', hours: 168, penalty: '' },
|
||
]
|
||
|
||
const deadlines = tiers.map(t => ({
|
||
...t,
|
||
deadline: new Date(faultTime.getTime() + t.hours * 3600_000),
|
||
}))
|
||
|
||
console.log('\n' + '═'.repeat(60))
|
||
console.log(' OEM 维修 — 完整生命周期模拟')
|
||
console.log('═'.repeat(60))
|
||
console.log(`工单号: ${orderNum}`)
|
||
console.log(`故障时间: ${bjTime(faultTime)}`)
|
||
deadlines.forEach(d => console.log(` ${d.name}截止: ${bjTime(d.deadline)} (${d.hours}h)`))
|
||
|
||
// 1. 初始故障通知(含5档截止时间)
|
||
const tierLines = deadlines.map((d, i) => {
|
||
const emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣']
|
||
return `${emojis[i]} ${d.desc}\n 截止时间:${bjTime(d.deadline)}(${d.hours}小时)`
|
||
}).join('\n\n')
|
||
|
||
await send('初始通知', [
|
||
`【服务器故障单】 - OEM维修`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`故障时间:${bjTime(faultTime)}`,
|
||
`故障信息:${fault}`,
|
||
'',
|
||
`📊 服务可用性各档位截止时间(基于月度内累计故障时长计算):`,
|
||
'',
|
||
tierLines,
|
||
].join('\n'))
|
||
|
||
// 2-5. 每个档位的提前1小时提醒
|
||
for (let i = 0; i < 4; i++) {
|
||
const t = deadlines[i]
|
||
const isFinalTier = t.name === '第四档'
|
||
const tierLabel = isFinalTier ? '全额扣费(100%)' : `${t.name}(${t.penalty})`
|
||
await send(`${t.name}提前1小时提醒`, [
|
||
`OEM维修工单超时提醒(距离${tierLabel}还剩1小时)`,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`${t.name}到期时间:${bjTime(t.deadline)}`,
|
||
'',
|
||
`服务器尚未录入恢复时间,请及时处理!`,
|
||
].join('\n'))
|
||
}
|
||
|
||
// 6-9. 每个档位的超时提醒
|
||
for (let i = 0; i < 4; i++) {
|
||
const t = deadlines[i]
|
||
const isFinalTier = t.name === '第四档'
|
||
const title = isFinalTier
|
||
? `OEM维修工单超时提醒(${t.name}已超时,已触发全额扣费(100%))`
|
||
: `OEM维修工单超时提醒(${t.name}已超时,达到${t.name}扣${t.penalty})`
|
||
await send(`${t.name}超时提醒`, [
|
||
title,
|
||
'',
|
||
`工单号:${orderNum}`,
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`${t.name}到期时间:${bjTime(t.deadline)}`,
|
||
'',
|
||
`服务器尚未录入恢复时间,请尽快处理!`,
|
||
].join('\n'))
|
||
}
|
||
|
||
// 10. 结单消息(含本月统计)
|
||
const closeTime = minusMinutes(now, -30)
|
||
const durationMin = Math.floor((closeTime.getTime() - faultTime.getTime()) / 60000)
|
||
const durH = Math.floor(durationMin / 60)
|
||
const durM = durationMin % 60
|
||
await send('结单消息', [
|
||
`✅ ${orderNum}工单已结单`,
|
||
'',
|
||
`服务器IP:${SERVER_IP}`,
|
||
`服务器SN:${SERVER_SN}`,
|
||
`机架位置:${RACK}`,
|
||
`故障类型:OEM维修`,
|
||
`故障时间:${bjTime(faultTime)}`,
|
||
`结单时间:${bjTime(closeTime)}`,
|
||
`本次处理时长:${durH}小时${durM}分钟`,
|
||
`本月故障次数:3次`,
|
||
`本月总处理时长:18小时30分钟`,
|
||
].join('\n'))
|
||
}
|
||
|
||
// ============================================================
|
||
// 主流程
|
||
// ============================================================
|
||
async function main() {
|
||
console.log('🚀 开始模拟企业微信消息推送')
|
||
console.log(`Webhook: ${WEBHOOK_URL.slice(0, 50)}...`)
|
||
console.log(`服务器: ${SERVER_IP} (${SERVER_SN})`)
|
||
console.log(`机架: ${RACK}`)
|
||
console.log(`间隔: ${INTERVAL / 1000}s`)
|
||
|
||
console.log('\n' + '═'.repeat(60))
|
||
console.log(' 第一部分:OEM 诊断(5 条消息)')
|
||
console.log('═'.repeat(60))
|
||
await runOemDiag()
|
||
|
||
console.log('\n⏸️ 等待 10 秒...\n')
|
||
await sleep(10000)
|
||
|
||
console.log('═'.repeat(60))
|
||
console.log(' 第二部分:OEM 维修(10 条消息)')
|
||
console.log('═'.repeat(60))
|
||
await runOemRepair()
|
||
|
||
console.log('\n' + '═'.repeat(60))
|
||
console.log('✅ 全部消息发送完成(共 15 条)')
|
||
console.log('═'.repeat(60))
|
||
}
|
||
|
||
main().catch(e => { console.error(e); process.exit(1) })
|