Files
fastify-beginner-guide/app.js

93 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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.

// 1. 【引入依赖】
const fastify = require('fastify')({ logger: true });
// undici 是 Node.js 内置的高性能 HTTP 客户端,我们用它来发请求
const { request: undiciRequest } = require('undici');
// 2. 【配置】- 从环境变量读取 Gotify 的信息
// 这是最佳实践,避免将敏感信息硬编码在代码里
// const GOTIFY_URL = process.env.GOTIFY_URL; // 例如: 'http://your-gotify-server.com/message'
// const GOTIFY_TOKEN = process.env.GOTIFY_TOKEN; // 你的 Gotify 应用 Token
const GOTIFY_URL = 'https://gotify.zotv.ru/message'; // 例如: 'http://your-gotify-server.com/message'
const GOTIFY_TOKEN = 'A1wFaeaj-VskqyF'; // 你的 Gotify 应用 Token
// 启动前检查配置是否齐全
if (!GOTIFY_URL || !GOTIFY_TOKEN) {
console.error('错误:请设置 GOTIFY_URL 和 GOTIFY_TOKEN 环境变量!');
process.exit(1);
}
// 3. 【核心路由】 - 接收和转发 Webhook
fastify.post('/webhook/feishu', async (request, reply) => {
fastify.log.info('收到来自飞书的 Webhook 请求...');
const feishuPayload = request.body;
let title = '来自飞书的新消息'; // 默认标题
let message = '';
// --- 解析飞书消息 ---
// A. 处理简单的 "text" 类型消息
if (feishuPayload.msg_type === 'text') {
message = feishuPayload.content?.text || '无法解析的文本消息';
}
// B. 处理 "interactive" (卡片消息) 类型
else if (feishuPayload.msg_type === 'interactive' && feishuPayload.card) {
// 尝试从卡片标题中获取标题
title = feishuPayload.card.header?.title?.content || title;
// 尝试从卡片的第一个元素中获取内容
// (这是一个简化的逻辑,真实卡片可能很复杂)
const firstElement = feishuPayload.card.elements?.[0];
if (firstElement?.tag === 'div' && firstElement.text) {
message = firstElement.text.content || '无法解析的卡片内容';
} else {
message = JSON.stringify(feishuPayload.card.elements); // 如果结构不认识,就发原始数据
}
}
// C. 其他未知类型
else {
fastify.log.warn('收到了未知的飞书消息类型将内容作为原始JSON转发');
message = JSON.stringify(feishuPayload, null, 2);
}
// --- 转发到 Gotify ---
const gotifyPayload = {
title: title,
message: message,
priority: 5, // Gotify 的消息优先级,可以按需调整
};
try {
fastify.log.info(`准备转发到 Gotify: ${GOTIFY_URL}`);
const { statusCode } = await undiciRequest(`${GOTIFY_URL}?token=${GOTIFY_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(gotifyPayload),
});
if (statusCode >= 200 && statusCode < 300) {
fastify.log.info('成功转发消息到 Gotify');
} else {
fastify.log.error(`转发到 Gotify 失败,状态码: ${statusCode}`);
}
} catch (error) {
fastify.log.error(`转发到 Gotify 时发生网络错误: ${error.message}`);
}
// --- 响应飞书 ---
// **非常重要**:无论转发是否成功,都应该立即回复飞书一个成功的响应。
// 否则飞书会认为你的 Webhook 地址有问题,并可能不停重试。
return { success: true, message: '消息已接收' };
});
// 4. 【启动服务器】
const start = async () => {
try {
// 监听 0.0.0.0 以便从局域网或 Docker 容器外访问
await fastify.listen({ port: 3000, host: '0.0.0.0' });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();