guanghulab/scripts/sync-patrol-to-notion.js

154 lines
4.3 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* sync-patrol-to-notion.js 同步巡检结果到 Notion
* 铸渊 · 光湖服务器巡检自动化
*
* 环境变量:
* NOTION_TOKEN Notion API Token
* NOTION_TICKET_DB_ID 霜砚工单队列 Database ID异常时自动开工单
*
* 读取 patrol-report.json写入 Notion 工单队列仅有异常时
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const NOTION_TOKEN = process.env.NOTION_TOKEN;
const TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID;
if (!NOTION_TOKEN) {
console.error('⚠️ 缺少环境变量: NOTION_TOKEN');
process.exit(1);
}
const REPORT_FILE = path.join(process.cwd(), 'patrol-report.json');
function notionRequest(method, endpoint, body) {
return new Promise((resolve, reject) => {
const data = body ? JSON.stringify(body) : null;
const options = {
hostname: 'api.notion.com',
path: `/v1/${endpoint}`,
method,
headers: {
'Authorization': `Bearer ${NOTION_TOKEN}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
},
};
if (data) {
options.headers['Content-Length'] = Buffer.byteLength(data);
}
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => { body += chunk; });
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(body) });
} catch {
resolve({ status: res.statusCode, data: body });
}
});
});
req.on('error', reject);
if (data) req.write(data);
req.end();
});
}
async function createTicket(alert, report) {
if (!TICKET_DB_ID) {
console.log(` ⚠️ 未配置 NOTION_TICKET_DB_ID跳过工单创建: ${alert}`);
return;
}
const res = await notionRequest('POST', 'pages', {
parent: { database_id: TICKET_DB_ID },
properties: {
title: { title: [{ text: { content: `🔍 服务器巡检异常: ${alert}` } }] },
'来源': { select: { name: '铸渊巡检' } },
'状态': { select: { name: '待处理' } },
'优先级': { select: { name: '高' } },
},
children: [
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{
text: {
content: `巡检时间: ${report.timestamp}\nNginx状态: ${report.nginx_status}\n磁盘使用率: ${report.disk_usage_percent}%\n\n异常详情: ${alert}`,
},
}],
},
},
],
});
if (res.status === 200) {
console.log(` ✅ 工单已创建: ${alert}`);
} else {
console.log(` ⚠️ 工单创建失败: ${alert} (HTTP ${res.status})`);
}
}
async function main() {
console.log('📝 sync-patrol-to-notion · 开始同步巡检结果');
if (!fs.existsSync(REPORT_FILE)) {
console.log('⚠️ patrol-report.json 不存在,跳过同步');
return;
}
let report;
try {
report = JSON.parse(fs.readFileSync(REPORT_FILE, 'utf-8'));
} catch (err) {
console.error('❌ 解析 patrol-report.json 失败:', err.message);
process.exit(1);
}
console.log(`📊 巡检状态: ${report.overall}`);
console.log(` 异常数: ${report.alert_count} | 自动修复: ${report.fixed_count}`);
if (report.overall === 'healthy') {
console.log('✅ 服务器健康,无需创建工单');
return;
}
// 找出未能自动修复的异常 → 开工单
const autoFixed = report.auto_fixed || [];
const unresolvedAlerts = (report.alerts || []).filter(alert => {
// 检查是否有对应的自动修复记录(修复消息中包含完整的异常描述关键词)
return !autoFixed.some(fix => {
const alertKey = alert.replace(/[:].*/g, '').trim();
return fix.includes(alertKey);
});
});
if (unresolvedAlerts.length === 0) {
console.log('✅ 所有异常已自动修复,无需创建工单');
return;
}
console.log(`📋 ${unresolvedAlerts.length} 个未修复异常,创建工单...`);
for (const alert of unresolvedAlerts) {
try {
await createTicket(alert, report);
} catch (err) {
console.log(` ⚠️ 工单创建异常: ${alert} (${err.message})`);
}
}
console.log('✅ 同步完成');
}
main().catch((err) => {
console.error('❌ sync-patrol-to-notion 异常:', err.message);
process.exit(1);
});