摘要
TrendRadar作为一个热点资讯聚合系统,其核心价值在于将筛选后的新闻资讯及时推送给用户。本文将深入分析TrendRadar的多渠道推送系统实现,包括企业微信、飞书、钉钉、Telegram、邮件等多种推送方式的技术细节和实现原理,帮助开发者理解如何构建一个灵活、可靠的多渠道消息推送系统。
正文
1. 引言
在信息过载的时代,如何确保重要信息能够及时、准确地传达给用户是系统设计的关键。TrendRadar通过支持多种推送渠道,确保用户能够在不同场景下接收到热点资讯,大大提升了系统的实用性和用户体验。
多渠道推送的优势:
- 覆盖不同用户群体的使用习惯
- 提高消息送达率
- 增强系统的容错能力
- 满足多样化的通知需求
2. 推送系统架构设计
TrendRadar的推送系统采用模块化设计,各推送渠道相对独立,便于维护和扩展。
2.1 系统架构图
2.2 核心组件
- 推送管理器:统一管理各种推送渠道
- 渠道适配器:针对不同渠道的具体实现
- 消息格式化器:将新闻数据转换为各渠道支持的格式
- 错误处理器:处理推送过程中的异常情况
3. 企业微信推送实现
企业微信是TrendRadar支持的主要推送渠道之一,具有部署简单、使用方便的特点。
3.1 机器人配置
def send_we_work_message(webhook_url, message):
"""
发送企业微信消息
Args:
webhook_url: 企业微信机器人Webhook地址
message: 消息内容
"""
headers = {
'Content-Type': 'application/json'
}
payload = {
"msgtype": "text",
"text": {
"content": message
}
}
try:
response = requests.post(webhook_url, json=payload, headers=headers)
if response.status_code == 200:
result = response.json()
if result.get("errcode") == 0:
return True, "发送成功"
else:
return False, f"发送失败: {result.get('errmsg')}"
else:
return False, f"HTTP错误: {response.status_code}"
except Exception as e:
return False, f"网络异常: {str(e)}"
3.2 消息分批处理
由于企业微信对消息长度有限制,需要对长消息进行分批处理:
def send_we_work_messages(webhook_url, messages, batch_size=4000):
"""
分批发送企业微信消息
Args:
webhook_url: 企业微信机器人Webhook地址
messages: 消息列表
batch_size: 每批消息的最大字符数
"""
current_batch = ""
batch_count = 1
for message in messages:
# 检查是否需要分批
if len(current_batch) + len(message) > batch_size:
# 发送当前批次
success, msg = send_we_work_message(
webhook_url,
f"[第{batch_count}批]\n{current_batch}"
)
if not success:
print(f"发送第{batch_count}批消息失败: {msg}")
# 开始新批次
current_batch = message
batch_count += 1
else:
current_batch += message + "\n"
# 发送最后一批
if current_batch:
success, msg = send_we_work_message(
webhook_url,
f"[第{batch_count}批]\n{current_batch}"
)
if not success:
print(f"发送第{batch_count}批消息失败: {msg}")
4. 飞书推送实现
飞书推送支持更丰富的消息格式,可以提供更好的用户体验。
4.1 消息格式化
def format_feishu_message(news_data):
"""
格式化飞书消息
Args:
news_data: 新闻数据
Returns:
dict: 飞书消息格式
"""
# 构建消息内容
content_lines = []
content_lines.append("📊 热点资讯推送")
content_lines.append("")
for i, news in enumerate(news_data, 1):
line = f"{i}. [{news['platform']}] {news['title']}"
if news.get('url'):
line += f" {news['url']}"
content_lines.append(line)
content_lines.append("")
content_lines.append(f"更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
return {
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "热点资讯",
"content": [
[{
"tag": "text",
"text": "\n".join(content_lines)
}]
]
}
}
}
}
4.2 推送实现
def send_feishu_message(webhook_url, message_data):
"""
发送飞书消息
Args:
webhook_url: 飞书机器人Webhook地址
message_data: 消息数据
"""
headers = {
'Content-Type': 'application/json'
}
try:
response = requests.post(webhook_url, json=message_data, headers=headers)
if response.status_code == 200:
result = response.json()
if result.get("code") == 0:
return True, "发送成功"
else:
return False, f"发送失败: {result.get('msg')}"
else:
return False, f"HTTP错误: {response.status_code}"
except Exception as e:
return False, f"网络异常: {str(e)}"
5. 钉钉推送实现
钉钉推送需要处理自定义关键词的限制。
5.1 消息构建
def send_dingtalk_message(webhook_url, message, keywords="热点"):
"""
发送钉钉消息
Args:
webhook_url: 钉钉机器人Webhook地址
message: 消息内容
keywords: 自定义关键词(钉钉机器人要求)
"""
headers = {
'Content-Type': 'application/json'
}
# 确保消息包含关键词
if keywords not in message:
message = f"{keywords}\n{message}"
payload = {
"msgtype": "text",
"text": {
"content": message
}
}
try:
response = requests.post(webhook_url, json=payload, headers=headers)
if response.status_code == 200:
result = response.json()
if result.get("errcode") == 0:
return True, "发送成功"
else:
return False, f"发送失败: {result.get('errmsg')}"
else:
return False, f"HTTP错误: {response.status_code}"
except Exception as e:
return False, f"网络异常: {str(e)}"
6. Telegram推送实现
Telegram推送通过Bot API实现,支持更丰富的交互功能。
6.1 Bot配置
def send_telegram_message(bot_token, chat_id, message):
"""
发送Telegram消息
Args:
bot_token: Telegram Bot Token
chat_id: 聊天ID
message: 消息内容
"""
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
payload = {
"chat_id": chat_id,
"text": message,
"parse_mode": "Markdown"
}
try:
response = requests.post(url, json=payload)
if response.status_code == 200:
result = response.json()
if result.get("ok"):
return True, "发送成功"
else:
return False, f"发送失败: {result.get('description')}"
else:
return False, f"HTTP错误: {response.status_code}"
except Exception as e:
return False, f"网络异常: {str(e)}"
7. 邮件推送实现
邮件推送支持HTML格式,可以提供更美观的展示效果。
7.1 SMTP配置
# SMTP邮件配置
SMTP_CONFIGS = {
# Gmail(使用 STARTTLS)
"gmail.com": {"server": "smtp.gmail.com", "port": 587, "encryption": "TLS"},
# QQ邮箱(使用 SSL,更稳定)
"qq.com": {"server": "smtp.qq.com", "port": 465, "encryption": "SSL"},
# Outlook(使用 STARTTLS)
"outlook.com": {
"server": "smtp-mail.outlook.com",
"port": 587,
"encryption": "TLS",
},
# 网易邮箱(使用 SSL,更稳定)
"163.com": {"server": "smtp.163.com", "port": 465, "encryption": "SSL"},
"126.com": {"server": "smtp.126.com", "port": 465, "encryption": "SSL"},
}
7.2 邮件发送
def send_email_notification(
from_email, password, to_emails, subject, html_content,
smtp_server="", smtp_port=""
):
"""
发送邮件通知
Args:
from_email: 发件人邮箱
password: 邮箱密码或授权码
to_emails: 收件人邮箱列表
subject: 邮件主题
html_content: HTML邮件内容
smtp_server: SMTP服务器地址
smtp_port: SMTP端口
"""
# 自动识别SMTP配置
domain = from_email.split('@')[-1]
if not smtp_server or not smtp_port:
if domain in SMTP_CONFIGS:
smtp_config = SMTP_CONFIGS[domain]
smtp_server = smtp_config["server"]
smtp_port = smtp_config["port"]
encryption = smtp_config["encryption"]
else:
return False, "不支持的邮箱服务商"
else:
encryption = "TLS" if int(smtp_port) == 587 else "SSL"
# 创建邮件
msg = MIMEMultipart('alternative')
msg['From'] = formataddr(('TrendRadar', from_email))
msg['To'] = ', '.join(to_emails)
msg['Subject'] = Header(subject, 'utf-8')
msg['Date'] = formatdate(localtime=True)
msg['Message-ID'] = make_msgid()
# 添加HTML内容
html_part = MIMEText(html_content, 'html', 'utf-8')
msg.attach(html_part)
try:
# 连接SMTP服务器
if encryption == "SSL":
server = smtplib.SMTP_SSL(smtp_server, smtp_port)
else:
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 登录并发送邮件
server.login(from_email, password)
server.sendmail(from_email, to_emails, msg.as_string())
server.quit()
return True, "邮件发送成功"
except Exception as e:
return False, f"邮件发送失败: {str(e)}"
8. ntfy推送实现
ntfy是一个开源的推送服务,支持自托管。
8.1 推送实现
def send_ntfy_message(server_url, topic, message, token=None):
"""
发送ntfy消息
Args:
server_url: ntfy服务器地址
topic: 主题名称
message: 消息内容
token: 访问令牌(可选)
"""
url = f"{server_url}/{topic}"
headers = {
'Content-Type': 'text/plain'
}
if token:
headers['Authorization'] = f'Bearer {token}'
try:
response = requests.post(url, data=message.encode('utf-8'), headers=headers)
if response.status_code == 200:
return True, "发送成功"
else:
return False, f"发送失败: {response.status_code} {response.text}"
except Exception as e:
return False, f"网络异常: {str(e)}"
9. 推送管理器实现
统一管理各种推送渠道:
class NotificationManager:
def __init__(self, config):
self.config = config
self.enabled_channels = self._get_enabled_channels()
def _get_enabled_channels(self):
"""获取启用的推送渠道"""
channels = []
if self.config.get("FEISHU_WEBHOOK_URL"):
channels.append("feishu")
if self.config.get("DINGTALK_WEBHOOK_URL"):
channels.append("dingtalk")
if self.config.get("WEWORK_WEBHOOK_URL"):
channels.append("wework")
if self.config.get("TELEGRAM_BOT_TOKEN") and self.config.get("TELEGRAM_CHAT_ID"):
channels.append("telegram")
if (self.config.get("EMAIL_FROM") and
self.config.get("EMAIL_PASSWORD") and
self.config.get("EMAIL_TO")):
channels.append("email")
return channels
def send_notifications(self, message, html_message=None):
"""发送通知到所有启用的渠道"""
results = {}
for channel in self.enabled_channels:
if channel == "feishu":
results[channel] = self._send_feishu(message)
elif channel == "dingtalk":
results[channel] = self._send_dingtalk(message)
elif channel == "wework":
results[channel] = self._send_wework(message)
elif channel == "telegram":
results[channel] = self._send_telegram(message)
elif channel == "email":
results[channel] = self._send_email(message, html_message)
return results
def _send_feishu(self, message):
"""发送飞书消息"""
webhook_url = self.config.get("FEISHU_WEBHOOK_URL")
# 实现飞书推送逻辑
pass
def _send_dingtalk(self, message):
"""发送钉钉消息"""
webhook_url = self.config.get("DINGTALK_WEBHOOK_URL")
# 实现钉钉推送逻辑
pass
# ... 其他渠道的实现 ...
10. 消息格式化与优化
10.1 统一消息格式
def format_notification_message(news_groups, report_type="daily"):
"""
格式化通知消息
Args:
news_groups: 分组的新闻数据
report_type: 报告类型
Returns:
tuple: (纯文本消息, HTML消息)
"""
# 构建文本消息
text_lines = []
text_lines.append("📊 热点资讯推送")
text_lines.append("=" * 30)
# 构建HTML消息
html_lines = []
html_lines.append("<h2>📊 热点资讯推送</h2>")
html_lines.append("<hr>")
for group in news_groups:
# 文本格式
text_lines.append(f"📈 {group['keywords']} : {group['count']} 条")
text_lines.append("")
# HTML格式
html_lines.append(f"<h3>📈 {group['keywords']} : {group['count']} 条</h3>")
html_lines.append("<ol>")
for i, news in enumerate(group['news'], 1):
# 文本格式
line = f" {i}. [{news['platform']}] {news['title']}"
if news.get('rank') and news['rank'] <= 5:
line += f" [**{news['rank']}**]"
text_lines.append(line)
# HTML格式
html_line = f"<li>[{news['platform']}] {news['title']}"
if news.get('url'):
html_line = f"<li><a href='{news['url']}'>[{news['platform']}] {news['title']}</a>"
if news.get('rank') and news['rank'] <= 5:
html_line += f" <strong>[{news['rank']}]</strong>"
html_line += "</li>"
html_lines.append(html_line)
text_lines.append("")
html_lines.append("</ol>")
html_lines.append("<br>")
text_lines.append(f"更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
html_lines.append(f"<p><em>更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</em></p>")
return "\n".join(text_lines), "\n".join(html_lines)
11. 错误处理与重试机制
11.1 重试机制
import time
from functools import wraps
def retry(max_attempts=3, delay=1):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise e
time.sleep(delay * (2 ** attempt)) # 指数退避
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def send_with_retry(channel, message):
"""带重试机制的消息发送"""
# 实际的发送逻辑
pass
12. 性能优化策略
12.1 异步推送
import asyncio
import aiohttp
async def send_notification_async(channel, message):
"""异步发送通知"""
# 异步推送实现
pass
async def send_all_notifications_async(messages):
"""并发发送所有通知"""
tasks = []
for channel, message in messages.items():
task = send_notification_async(channel, message)
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
13. 安全考虑
13.1 Webhook URL保护
import os
from urllib.parse import urlparse
def validate_webhook_url(url):
"""验证Webhook URL的安全性"""
try:
parsed = urlparse(url)
# 检查协议
if parsed.scheme not in ['https', 'http']:
return False, "不支持的协议"
# 检查域名白名单(可选)
allowed_domains = os.getenv('ALLOWED_WEBHOOK_DOMAINS', '').split(',')
if allowed_domains and parsed.netloc not in allowed_domains:
return False, "域名不在白名单中"
return True, "验证通过"
except Exception as e:
return False, f"URL格式错误: {str(e)}"
总结
TrendRadar的多渠道推送系统通过模块化设计和统一接口,实现了对企业微信、飞书、钉钉、Telegram、邮件等多种推送渠道的支持。该系统具有良好的扩展性、可靠性和安全性,能够满足不同用户群体的通知需求。通过合理的错误处理、重试机制和性能优化,确保了消息推送的稳定性和及时性。
参考资料
- TrendRadar GitHub仓库:https://github.com/sansan0/TrendRadar
- 企业微信机器人文档:https://work.weixin.qq.com/api/doc
- 飞书机器人文档:https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN
- 钉钉机器人文档:https://developers.dingtalk.com/document/app/custom-robot-access
- Telegram Bot API:https://core.telegram.org/bots/api
- ntfy文档:https://docs.ntfy.sh/
1万+

被折叠的 条评论
为什么被折叠?



