feat: 实现企业微信消息推送功能,支持Markdown格式

This commit is contained in:
huzhengrong 2025-10-23 20:08:13 +08:00
parent 981d7f1c2a
commit 924da331c7
2 changed files with 25 additions and 67 deletions

View File

@ -2,7 +2,8 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { EmailSender } from "./mailer.js"; // import { EmailSender } from "./mailer.js";
import { sendQYWechatMessage } from "./utils.js"
import { SQLiteMessageQueue } from "./sqlite.js"; import { SQLiteMessageQueue } from "./sqlite.js";
import { md5 } from "./utils.js"; import { md5 } from "./utils.js";
import axios from "axios"; import axios from "axios";
@ -13,15 +14,15 @@ class MessageQueue extends EventEmitter {
this.queue = new SQLiteMessageQueue(); this.queue = new SQLiteMessageQueue();
this.processing = false; this.processing = false;
// this.queueFile = path.resolve("message_queue.json");K // this.queueFile = path.resolve("message_queue.json");K
this.emailSender = new EmailSender({ // this.emailSender = new EmailSender({
host: "smtp.exmail.qq.com", // host: "smtp.exmail.qq.com",
port: 465, // port: 465,
secure: true, // secure: true,
auth: { // auth: {
user: "jiqiren@axbbaoxian.com", // user: "jiqiren@axbbaoxian.com",
pass: "Am13579q", // pass: "Am13579q",
}, // },
}); // });
this.recipients = [ this.recipients = [
"huzhengrong@axbbaoxian.com", "huzhengrong@axbbaoxian.com",
]; ];
@ -88,7 +89,8 @@ class MessageQueue extends EventEmitter {
html += this.generateTable(spiderName, msgMap[spiderName]); html += this.generateTable(spiderName, msgMap[spiderName]);
} }
try { try {
this.emailSender.sendBulkEmail(this.recipients, "招标项目最新公告", html); // this.emailSender.sendBulkEmail(this.recipients, "招标项目最新公告", html);
await sendQYWechatMessage(html)
} catch (error) { } catch (error) {
console.error(`❌ 通知发送失败: ${error}`); console.error(`❌ 通知发送失败: ${error}`);
} }
@ -97,62 +99,18 @@ class MessageQueue extends EventEmitter {
} }
generateTable(spiderName, data) { generateTable(spiderName, data) {
let tableHtml = ` let tableHtml =`
<div style="margin: 30px 0; font-family: Arial, sans-serif;"> 各位好
<h2 style="color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; margin-bottom: 20px;"> 本次检测发现了${data.length}条新增招标信息详情如下
🕷 ${spiderName} (${data.length} 条新增) `
</h2>
<div style="overflow-x: auto; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; margin-bottom: 20px;">
<table style="width: 100%; border-collapse: collapse; background: white; min-width: 800px;">
<thead>
<tr style="background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); color: white;">
<th style="border: 1px solid #ddd; padding: 12px 8px; text-align: left; font-weight: bold; width: 50px;">序号</th>
<th style="border: 1px solid #ddd; padding: 12px 8px; text-align: left; font-weight: bold; min-width: 300px;">项目名称</th>
<th style="border: 1px solid #ddd; padding: 12px 8px; text-align: left; font-weight: bold; width: 140px;">发布时间</th>
<th style="border: 1px solid #ddd; padding: 12px 8px; text-align: left; font-weight: bold; width: 140px;">截止时间</th>
<th style="border: 1px solid #ddd; padding: 12px 8px; text-align: left; font-weight: bold; width: 100px;">查看详情</th>
</tr>
</thead>
<tbody>
`;
data.forEach((item, index) => { data.forEach((item, index) => {
const rowColor = index % 2 === 0 ? "#f8f9fa" : "white";
// const publishTime = this.formatDateTime(item.publishTime);
// const endTime = this.formatDateTime(item.endTime);
const urls = this.formatUrls(item.urls);
tableHtml += ` tableHtml += `
<tr style="background-color: ${rowColor}; border-bottom: 1px solid #eee;"> ${index+1}
<td style="border: 1px solid #ddd; padding: 10px 8px; text-align: center; font-weight: bold; color: #666;"> 保司${spiderName}
${index + 1} 招标标题${item.name}
</td> 详情链接[链接](${item.urls})
<td style="border: 1px solid #ddd; padding: 10px 8px; line-height: 1.4;">
<div style="font-weight: 500; color: #2c3e50; margin-bottom: 4px;">
${item.name}
</div>
</td>
<td style="border: 1px solid #ddd; padding: 10px 8px; color: #495057;">
${item.publishTime}
</td>
<td style="border: 1px solid #ddd; padding: 10px 8px; color: #495057;">
<div>${item.endTime}</div>
</td>
<td style="border: 1px solid #ddd; padding: 10px 8px; text-align: center;">
${urls}
</td>
</tr>
`; `;
}); });
tableHtml += `
</tbody>
</table>
</div>
</div>
`;
return tableHtml; return tableHtml;
} }

View File

@ -255,8 +255,8 @@ async function sendQYWechatMessage(message) {
try { try {
const webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=7de26c43-8652-4204-9665-47a5cef58b58"; const webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=7de26c43-8652-4204-9665-47a5cef58b58";
await axios.post(webhook, { await axios.post(webhook, {
msgtype: "text", msgtype: "markdown",
text: { markdown: {
content: message content: message
} }
}, { }, {
@ -264,9 +264,9 @@ async function sendQYWechatMessage(message) {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}); });
console.log(`${this.key}-企业微信消息推送成功: ${message}`); console.log(`企业微信消息推送成功: ${message}`);
} catch (error) { } catch (error) {
console.error(`${this.key}-企业微信消息推送失败:`, error.message); console.error(`企业微信消息推送失败:`, error.message);
} }
} }
export { export {