202 lines
7.0 KiB
JavaScript
202 lines
7.0 KiB
JavaScript
// scripts/server-diagnose-report.js
|
||
// 铸渊 · PM2 服务诊断报告 → Notion 工单
|
||
//
|
||
// 将 PM2 诊断和系统健康检查结果写入 Notion 工单队列。
|
||
// 由 pm2-server-diagnose.yml 工作流调用。
|
||
//
|
||
// 环境变量:
|
||
// NOTION_TOKEN Notion API token
|
||
// NOTION_TICKET_DB_ID 工单队列数据库 ID
|
||
// PM2_REPORT PM2 诊断输出
|
||
// CLEANUP_REPORT 清理操作输出(可选)
|
||
// HEALTH_REPORT 健康检查输出
|
||
|
||
'use strict';
|
||
|
||
const https = require('https');
|
||
|
||
const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
|
||
const NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || '';
|
||
const PM2_REPORT = process.env.PM2_REPORT || '';
|
||
const CLEANUP_REPORT = process.env.CLEANUP_REPORT || '';
|
||
const HEALTH_REPORT = process.env.HEALTH_REPORT || '';
|
||
|
||
const NOTION_VERSION = '2022-06-28';
|
||
const NOTION_API_HOSTNAME = 'api.notion.com';
|
||
const NOTION_RICH_TEXT_MAX = 2000;
|
||
const MAX_PM2_REPORT_LENGTH = 3000;
|
||
const MAX_CLEANUP_REPORT_LENGTH = 2000;
|
||
const MAX_HEALTH_REPORT_LENGTH = 3000;
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// Notion API 工具
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
function notionPost(endpoint, body) {
|
||
return new Promise(function (resolve, reject) {
|
||
var payload = JSON.stringify(body);
|
||
var opts = {
|
||
hostname: NOTION_API_HOSTNAME,
|
||
port: 443,
|
||
path: endpoint,
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + NOTION_TOKEN,
|
||
'Content-Type': 'application/json',
|
||
'Notion-Version': NOTION_VERSION,
|
||
'Content-Length': Buffer.byteLength(payload),
|
||
},
|
||
};
|
||
var req = https.request(opts, function (res) {
|
||
var data = '';
|
||
res.on('data', function (chunk) { data += chunk; });
|
||
res.on('end', function () {
|
||
try {
|
||
var parsed = JSON.parse(data);
|
||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||
resolve(parsed);
|
||
} else {
|
||
reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
|
||
}
|
||
} catch (e) {
|
||
reject(new Error('Notion API parse error: ' + data));
|
||
}
|
||
});
|
||
});
|
||
req.on('error', reject);
|
||
req.write(payload);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
function richTextChunks(content) {
|
||
var str = String(content || '');
|
||
var chunks = [];
|
||
for (var i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
|
||
chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
|
||
}
|
||
if (chunks.length === 0) {
|
||
chunks.push({ type: 'text', text: { content: '' } });
|
||
}
|
||
return chunks;
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 构建报告内容
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
function buildReportContent() {
|
||
var now = new Date().toISOString();
|
||
var dateStr = now.slice(0, 10);
|
||
|
||
var sections = [];
|
||
|
||
sections.push('## 📋 PM2 服务排查报告 · ' + dateStr);
|
||
sections.push('');
|
||
sections.push('| 字段 | 值 |');
|
||
sections.push('|------|-----|');
|
||
sections.push('| 执行者 | 铸渊Agent |');
|
||
sections.push('| 执行时间 | ' + now + ' |');
|
||
sections.push('| 来源 | GitHub Actions · pm2-server-diagnose |');
|
||
sections.push('');
|
||
|
||
if (PM2_REPORT) {
|
||
sections.push('## 阶段一 · PM2 诊断结果');
|
||
sections.push('');
|
||
sections.push(PM2_REPORT.slice(0, MAX_PM2_REPORT_LENGTH));
|
||
sections.push('');
|
||
}
|
||
|
||
if (CLEANUP_REPORT) {
|
||
sections.push('## 清理操作结果');
|
||
sections.push('');
|
||
sections.push(CLEANUP_REPORT.slice(0, MAX_CLEANUP_REPORT_LENGTH));
|
||
sections.push('');
|
||
}
|
||
|
||
if (HEALTH_REPORT) {
|
||
sections.push('## 阶段二 · 全系统健康检查');
|
||
sections.push('');
|
||
sections.push(HEALTH_REPORT.slice(0, MAX_HEALTH_REPORT_LENGTH));
|
||
sections.push('');
|
||
}
|
||
|
||
return sections.join('\n');
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 主函数 — 创建 Notion 诊断工单
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
async function createDiagnoseTicket() {
|
||
if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
|
||
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
|
||
console.log(' NOTION_TOKEN: ' + (NOTION_TOKEN ? '✅' : '❌'));
|
||
console.log(' NOTION_TICKET_DB_ID: ' + (NOTION_TICKET_DB_ID ? '✅' : '❌'));
|
||
process.exit(0);
|
||
}
|
||
|
||
var now = new Date();
|
||
var dateStr = now.toISOString().slice(0, 10);
|
||
var title = 'PM2服务排查报告 · ' + dateStr;
|
||
var contentText = buildReportContent();
|
||
|
||
// 将内容拆分成多个段落块(Notion 单个 rich_text 限制 2000 字符)
|
||
var contentBlocks = [];
|
||
var remaining = contentText;
|
||
|
||
while (remaining.length > 0) {
|
||
var chunk = remaining.slice(0, NOTION_RICH_TEXT_MAX);
|
||
remaining = remaining.slice(NOTION_RICH_TEXT_MAX);
|
||
contentBlocks.push({
|
||
object: 'block',
|
||
type: 'paragraph',
|
||
paragraph: {
|
||
rich_text: [{ type: 'text', text: { content: chunk } }],
|
||
},
|
||
});
|
||
}
|
||
|
||
// 确保至少有一个内容块
|
||
if (contentBlocks.length === 0) {
|
||
contentBlocks.push({
|
||
object: 'block',
|
||
type: 'paragraph',
|
||
paragraph: {
|
||
rich_text: [{ type: 'text', text: { content: '诊断报告(无详细输出)' } }],
|
||
},
|
||
});
|
||
}
|
||
|
||
var body = {
|
||
parent: { database_id: NOTION_TICKET_DB_ID },
|
||
properties: {
|
||
'标题': { title: [{ type: 'text', text: { content: title } }] },
|
||
'操作类型': { select: { name: '其他' } },
|
||
'提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent' } }] },
|
||
'状态': { select: { name: '已完成' } },
|
||
'优先级': { select: { name: 'P1' } },
|
||
},
|
||
children: contentBlocks,
|
||
};
|
||
|
||
console.log('📋 创建 PM2 诊断工单...');
|
||
console.log(' 标题: ' + title);
|
||
console.log(' 数据库: ' + NOTION_TICKET_DB_ID);
|
||
console.log(' 内容长度: ' + contentText.length + ' 字符');
|
||
console.log(' 内容块数: ' + contentBlocks.length);
|
||
|
||
try {
|
||
var result = await notionPost('/v1/pages', body);
|
||
console.log('✅ Notion 诊断工单已创建: ' + result.id);
|
||
console.log(' URL: ' + (result.url || 'N/A'));
|
||
} catch (err) {
|
||
console.error('❌ 工单创建失败: ' + err.message);
|
||
// 不以失败退出,诊断本身已成功
|
||
console.log('⚠️ 诊断结果已输出到 Actions 日志,Notion 写入失败不影响诊断');
|
||
process.exit(0);
|
||
}
|
||
}
|
||
|
||
createDiagnoseTicket();
|