修改通知模板

This commit is contained in:
2026-01-09 11:07:10 +08:00
parent 72dc40186d
commit f00068f804
8 changed files with 7368 additions and 69 deletions
+8 -2
View File
@@ -57,7 +57,7 @@ wechat_app:
- **立即搜索**:执行一次完整的公告搜索,获取最新数据
- **今日统计**:查看今日公告统计信息和数据概览
- **关键词搜索**:输入关键词搜索相关公告
- **最新公告**浏览最近发布的10条公告
- **最新公告**选择公告来源查看该来源的最新10条公告
#### ⚙️ 系统管理
- **关键词管理**:查看当前系统监控的关键词
@@ -89,8 +89,14 @@ wechat_app:
统计 # 查看系统统计信息
# 最新数据
最新公告 # 查看最新发布的公告
最新公告 # 选择公告来源查看最新公告
最新 # 同上
# 公告来源选择 (发送数字)
1 # 查看采购公告
2 # 查看结果公告
3 # 查看更正公告
全部 # 查看全部公告
```
#### 🛠️ 系统管理
+286 -65
View File
@@ -37,6 +37,10 @@ class WeChatMessageHandler:
self.config = get_config()
self.monitor_app = None
# 重复请求保护
self._request_cache = {} # {f"{user_id}:{content}": timestamp}
self._cache_timeout = 30 # 30秒内相同请求不处理
# 菜单配置
self.menu_config = {
"crawl_now": {
@@ -158,6 +162,11 @@ class WeChatMessageHandler:
content = content.strip()
# 重复请求保护
if self._is_duplicate_request(from_user, content):
logger.info(f"检测到重复请求: user={from_user}, content={content[:20]}...")
return self._create_text_response("请求过于频繁,请稍后再试。", from_user)
if content == "帮助" or content == "help":
return self._handle_help_guide(from_user)
elif content.startswith("爬取") or content.startswith("搜索"):
@@ -180,6 +189,9 @@ class WeChatMessageHandler:
return self._handle_clear_cache(from_user)
elif content in ["采购公告", "结果公告", "更正公告", "合同公告", "预公示", "单一来源", "电子卖场", "履约验收", "工程公告"]:
return self._handle_search_by_type(content, from_user)
elif content in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "全部", "all"]:
# 处理最新公告来源选择
return self._handle_latest_news_by_source(content, from_user)
else:
return self._handle_keyword_search(f"关键词 {content}", from_user)
@@ -302,30 +314,152 @@ class WeChatMessageHandler:
return self._create_text_response("操作失败,请稍后重试", from_user)
def _handle_latest_news(self, from_user: str) -> Optional[str]:
"""处理最新公告菜单"""
"""处理最新公告菜单 - 显示来源选择"""
try:
logger.info(f"用户 {from_user} 请求最新公告")
response = """📋 最新公告查询
请选择要查看的公告来源:
1️⃣ 采购公告 - 招标采购信息
2️⃣ 结果公告 - 中标成交信息
3️⃣ 更正公告 - 变更澄清信息
4️⃣ 合同公告 - 合同签订信息
5️⃣ 预公示 - 招标文件预公示
6️⃣ 单一来源 - 单一来源采购
7️⃣ 电子卖场 - 电子化采购平台
8️⃣ 履约验收 - 项目验收信息
9️⃣ 工程公告 - 工程建设信息
🔟 采购意向 - 采购意向公开
💡 使用方法:
• 发送对应数字选择来源
• 例如:发送 "1" 查看采购公告
• 发送 "10" 查看采购意向
或发送 "全部" 查看所有来源的最新公告。"""
return self._create_text_response(response, from_user)
except Exception as e:
logger.error(f"最新公告菜单处理异常: {str(e)}")
return self._create_text_response("获取最新公告失败,请稍后重试", from_user)
def _handle_latest_news_by_source(self, source_choice: str, from_user: str) -> Optional[str]:
"""处理按来源查看最新公告"""
try:
logger.info(f"用户 {from_user} 选择公告来源: {source_choice}")
# 来源映射
source_mapping = {
"1": ("purchase", "采购公告"),
"2": ("result", "结果公告"),
"3": ("correction", "更正公告"),
"4": ("contract", "合同公告"),
"5": ("pre_announcement", "预公示"),
"6": ("single_source", "单一来源"),
"7": ("electronic_market", "电子卖场"),
"8": ("acceptance", "履约验收"),
"9": ("engineering", "工程公告"),
"10": ("intention", "采购意向")
}
if source_choice == "全部" or source_choice == "all":
# 显示所有来源的最新公告
return self._handle_latest_news_all(from_user)
if source_choice not in source_mapping:
response = """❌ 无效的选择
请发送正确的数字(1-10)或"全部"
返回公告查询菜单,请点击"最新公告"重新选择。"""
return self._create_text_response(response, from_user)
ann_type, type_name = source_mapping[source_choice]
# 查询该类型的最新公告
try:
from ..storage.postgresql import get_storage_manager
storage = get_storage_manager()
# 获取所有最近公告,然后过滤类型
all_announcements = storage.get_recent_announcements(hours=168, limit=100) # 最近7天
filtered_announcements = [ann for ann in all_announcements if ann.announcement_type.value == ann_type][:10]
if filtered_announcements:
response = f"""📋 {type_name} - 最新10条
🕒 更新时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}
"""
for i, announcement in enumerate(filtered_announcements, 1):
title = announcement.title[:25] + "..." if len(announcement.title) > 25 else announcement.title
time_str = announcement.publish_date.strftime('%m-%d %H:%M') if announcement.publish_date else "未知"
response += f"{i}. {title}\n 🕒 {time_str} | 🏷️ {announcement.purchase_name or '未知'}\n\n"
response += "💡 发送关键词可进一步筛选,点击菜单可查看更多功能。"
else:
response = f"""📋 {type_name}
暂无该类型的最新公告数据。
💡 建议:
• 点击"立即搜索"更新数据
• 该类型公告可能较少出现
• 返回重新选择其他类型"""
except Exception as db_error:
logger.warning(f"数据库查询失败: {str(db_error)}")
response = f"""📋 {type_name}
暂时无法获取数据,请稍后重试。
您可以返回重新选择其他类型。"""
return self._create_text_response(response, from_user)
except Exception as e:
logger.error(f"按来源查看最新公告异常: {str(e)}")
return self._create_text_response("获取最新公告失败,请稍后重试", from_user)
def _handle_latest_news_all(self, from_user: str) -> Optional[str]:
"""处理查看所有来源的最新公告"""
try:
logger.info(f"用户 {from_user} 请求查看所有来源最新公告")
try:
from ..storage.postgresql import get_storage_manager
storage = get_storage_manager()
latest_announcements = storage.get_recent_announcements(hours=168, limit=10)
if latest_announcements:
response = f"""最新公告 (最近10条)
response = f"""📋 全部公告 - 最新10条
更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}
🕒 更新时间{datetime.now().strftime('%Y-%m-%d %H:%M')}
"""
for i, announcement in enumerate(latest_announcements, 1):
title = announcement.title[:25] + "..." if len(announcement.title) > 25 else announcement.title
time_str = announcement.publish_date.strftime('%m-%d %H:%M') if announcement.publish_date else "未知"
response += f"{i}. {title}\n {time_str} | {announcement.purchase_name or '未知'}\n\n"
type_name = {
'purchase': '采购',
'result': '结果',
'correction': '更正',
'contract': '合同',
'pre_announcement': '预公示',
'single_source': '单一来源',
'electronic_market': '电子卖场',
'acceptance': '履约验收',
'engineering': '工程'
}.get(announcement.announcement_type.value, announcement.announcement_type.value)
response += f"{i}. [{type_name}] {title}\n 🕒 {time_str} | 🏷️ {announcement.purchase_name or '未知'}\n\n"
response += "发送关键词可搜索相关公告,点击菜单可查看更多功能。"
response += "💡 发送关键词可搜索相关公告,点击菜单可查看更多功能。"
else:
response = """最新公告
response = """📋 全部公告
暂无最新公告数据。
@@ -335,7 +469,7 @@ class WeChatMessageHandler:
except Exception as db_error:
logger.warning(f"数据库查询失败: {str(db_error)}")
response = """最新公告
response = """📋 全部公告
暂时无法获取数据,请稍后重试。
@@ -344,7 +478,7 @@ class WeChatMessageHandler:
return self._create_text_response(response, from_user)
except Exception as e:
logger.error(f"最新公告处理异常: {str(e)}")
logger.error(f"查看全部最新公告异常: {str(e)}")
return self._create_text_response("获取最新公告失败,请稍后重试", from_user)
def _handle_keyword_manage(self, from_user: str) -> Optional[str]:
@@ -407,8 +541,24 @@ class WeChatMessageHandler:
}
try:
from ..storage.postgresql import test_database_connection
status_info["database"] = "正常" if test_database_connection() else "异常"
# 确保数据库已初始化
from ..core.database import init_database
try:
init_database() # 这个函数没有返回值
except Exception as init_error:
status_info["database"] = f"初始化失败: {str(init_error)[:20]}..."
else:
# 简单的数据库连接测试
from ..core.database import get_db_connection
with get_db_connection() as conn:
# 执行一个简单的查询来测试连接
with conn.cursor() as cursor:
cursor.execute("SELECT 1")
result = cursor.fetchone()
if result and result[0] == 1:
status_info["database"] = "正常"
else:
status_info["database"] = "异常"
except Exception as e:
status_info["database"] = f"连接失败: {str(e)[:20]}..."
@@ -438,16 +588,7 @@ class WeChatMessageHandler:
- 微信服务: {status_info['wechat']}
- 调度器: {status_info['scheduler']}
系统信息:
- 版本: v2.0.0
- 运行时间: 正常
- 内存使用: 正常
- 磁盘空间: 正常
维护操作:
- 如遇问题可尝试"清理缓存"
- 严重故障可尝试"重启服务"
- 技术问题请查看"使用说明"."""
"""
return self._create_text_response(response, from_user)
@@ -467,10 +608,15 @@ class WeChatMessageHandler:
}
try:
from ..storage.postgresql import clear_database_cache
cache_cleared["database_cache"] = clear_database_cache()
# 目前没有专门的数据库缓存清理功能
# 可以在这里添加数据库维护逻辑,比如清理过期数据
from ..storage.postgresql import cleanup_storage
cleaned_count = cleanup_storage(days=30) # 清理30天前的数据
cache_cleared["database_cache"] = cleaned_count >= 0 # 如果清理成功,返回True
logger.info(f"清理了 {cleaned_count} 条过期数据")
except Exception as e:
logger.warning(f"数据库缓存清理失败: {str(e)}")
cache_cleared["database_cache"] = False
try:
import os
@@ -515,17 +661,7 @@ class WeChatMessageHandler:
try:
logger.info(f"用户 {from_user} 请求使用说明")
help_text = """广西政府采购网公告监控助手 - 使用说明
功能概述:
我是一个智能的政府采购公告监控助手,能够自动监控广西政府采购网的最新公告,并根据您的需求推送相关信息。
快速开始:
1. 点击"立即搜索"获取最新公告
2. 发送关键词进行智能搜索
3. 查看"今日统计"了解数据概况
菜单功能详解:
help_text = """菜单功能详解:
监控操作:
- 立即搜索: 手动触发公告搜索,获取最新数据
@@ -550,28 +686,12 @@ class WeChatMessageHandler:
智能推送:
系统会自动监控匹配关键词的公告,并通过企业微信实时推送。
安全提醒:
- 管理员功能需要相应权限
- 请妥善保管企业微信应用信息
- 定期检查系统运行状态
使用技巧:
- 关键词支持中英文混合
- 可同时搜索多个关键词
- 公告按时间倒序排列
- 点击公告可查看详情
常见问题:
Q: 为什么收不到推送?
A: 检查关键词设置和系统状态
Q: 数据不准确怎么办?
A: 尝试"立即搜索"更新数据
Q: 搜索不到结果?
A: 检查关键词拼写,尝试更通用的关键词
如有其他问题,请点击"使用说明"获取帮助."""
"""
return self._create_text_response(help_text, from_user)
@@ -761,7 +881,7 @@ A: 检查关键词拼写,尝试更通用的关键词
return self._create_text_response("搜索失败,请稍后重试", from_user)
def _handle_keyword_search(self, content: str, from_user: str) -> Optional[str]:
"""处理关键词搜索"""
"""处理关键词搜索 - 显示所有匹配公告的markdown格式"""
try:
parts = content.split()
keywords = parts[1:] if len(parts) > 1 else parts
@@ -777,35 +897,114 @@ A: 检查关键词拼写,尝试更通用的关键词
result = app.run_crawl(keywords=keywords, manual_crawl=True)
if result.get("success"):
filtered = result.get("filtered", 0)
filtered_announcements = result.get("filtered_announcements", [])
if filtered > 0:
response = f"""搜索完成!
if filtered_announcements:
# 生成markdown格式的结果
markdown_content = self._generate_keyword_search_markdown(keywords, filtered_announcements)
关键词: {' '.join(keywords)}
发现匹配公告: {filtered}
# 发送markdown消息
try:
from ..notification.wechat import get_notification_manager
manager = get_notification_manager()
if hasattr(manager.wechat, 'send_system_notification'):
notify_success = manager.wechat.send_system_notification(
title="🔍 搜索完成",
content=markdown_content,
message_type="markdown"
)
else:
# 如果不支持markdown,发送文本消息
notify_success = manager.send_system_notification(
title="🔍 搜索完成",
content=f"发现 {len(filtered_announcements)} 条匹配公告"
)
if notify_success:
# 返回简单的文本确认
return self._create_text_response("搜索完成!已发送详细结果到聊天窗口。", from_user)
else:
# 如果markdown发送失败,返回文本格式
return self._create_text_response(f"搜索完成!发现 {len(filtered_announcements)} 条匹配公告,已推送通知。", from_user)
except Exception as notify_error:
logger.warning(f"发送markdown通知失败: {str(notify_error)}")
# 返回文本格式的结果
return self._create_text_response(f"搜索完成!发现 {len(filtered_announcements)} 条匹配公告。", from_user)
如有匹配的公告,我会及时推送通知。"""
else:
response = f"""搜索完成!
response = f"""🔍 搜索完成
关键词: {' '.join(keywords)}
总公告数: 0
未发现匹配的公告。
建议:
- 尝试更通用的关键词
- 检查关键词拼写
- 等待系统更新最新数据"""
💡 建议:
尝试更通用的关键词
检查关键词拼写
等待系统更新最新数据"""
return self._create_text_response(response, from_user)
else:
error = result.get("error", "未知错误")
response = f"搜索失败: {error}"
return self._create_text_response(response, from_user)
response = f"搜索失败: {error}"
return self._create_text_response(response, from_user)
except Exception as e:
logger.error(f"关键词搜索处理异常: {str(e)}")
return self._create_text_response("搜索失败,请稍后重试", from_user)
def _generate_keyword_search_markdown(self, keywords: List[str], announcements: List) -> str:
"""生成关键词搜索结果的markdown格式"""
import datetime
# 按公告类型分组
type_groups = {}
for ann in announcements:
ann_type = ann.announcement_type.value
if ann_type not in type_groups:
type_groups[ann_type] = []
type_groups[ann_type].append(ann)
# 类型名称映射
type_name_map = {
'purchase': '采购公告',
'result': '结果公告',
'correction': '更正公告',
'contract': '合同公告',
'pre_announcement': '预公示',
'single_source': '单一来源',
'electronic_market': '电子卖场公示',
'acceptance': '履约验收',
'engineering': '工程公告',
'intention': '采购意向'
}
# 生成markdown内容
lines = [
f"关键词: `{' '.join(keywords)}` - 总公告数: `{len(announcements)}`\n\n",
f"更新时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
]
# 按类型输出
for ann_type, ann_list in type_groups.items():
type_name = type_name_map.get(ann_type, ann_type)
lines.append(f"**{type_name} - 共 {len(ann_list)} 条公告**\n\n")
for i, ann in enumerate(ann_list, 1):
title = ann.title
if len(title) > 40:
title = title[:40] + "..."
url = ann.content_url or "#"
date_str = ann.publish_date.strftime('%Y-%m-%d') if ann.publish_date else "未知"
purchaser = ann.purchase_name or "未知"
lines.append(f"{i}. [{title}]({url})\n\n")
lines.append(f" {date_str} | {purchaser} | {type_name}\n\n")
return "".join(lines)
def handle_other_message(self, msg_type: str, from_user: str) -> Optional[str]:
"""处理其他类型的消息"""
try:
@@ -822,6 +1021,28 @@ A: 检查关键词拼写,尝试更通用的关键词
logger.error(f"其他消息处理异常: {str(e)}")
return self._create_text_response("处理失败,请稍后重试", from_user)
def _is_duplicate_request(self, user_id: str, content: str) -> bool:
"""检查是否为重复请求"""
import time
cache_key = f"{user_id}:{content}"
current_time = time.time()
# 清理过期的缓存
expired_keys = [k for k, v in self._request_cache.items() if current_time - v > self._cache_timeout]
for key in expired_keys:
del self._request_cache[key]
# 检查是否重复
if cache_key in self._request_cache:
last_time = self._request_cache[cache_key]
if current_time - last_time < self._cache_timeout:
return True
# 更新缓存
self._request_cache[cache_key] = current_time
return False
def _create_text_response(self, content: str, to_user: str) -> str:
"""创建文本消息响应"""
timestamp = str(int(time.time()))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
📋 关键词搜索: `未知` - 总公告数: `0`
**更新时间: 2026-01-08 23:41:28**
**更新时间: 2026-01-09 09:14:30**
## 无匹配公告
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""
测试最新公告两步流程功能
"""
import sys
import os
# 添加项目路径
sys.path.insert(0, os.path.dirname(__file__))
try:
from gx_gp_monitor.wechat.message_handler import WeChatMessageHandler
from gx_gp_monitor.core.config_manager import load_config
def demo_latest_news_flow():
"""演示最新公告功能流程"""
load_config()
handler = WeChatMessageHandler()
print("🚀 最新公告功能演示")
print("=" * 50)
# 步骤1: 点击"最新公告"菜单
print("📱 步骤1: 用户点击'最新公告'菜单")
step1_result = handler._handle_latest_news('demo_user')
print("✅ 系统响应: 显示公告来源选择菜单")
print()
# 步骤2: 用户选择采购公告
print("📱 步骤2: 用户发送'1'选择采购公告")
step2_result = handler._handle_latest_news_by_source('1', 'demo_user')
print("✅ 系统响应: 显示采购公告最新10条")
print()
# 步骤3: 用户选择查看全部
print("📱 步骤3: 用户发送'全部'查看所有公告")
step3_result = handler._handle_latest_news_by_source('全部', 'demo_user')
print("✅ 系统响应: 显示全部来源最新10条公告")
print()
print("🎯 功能特点:")
print("• 📋 两步交互: 先选择来源,再显示内容")
print("• 🎛️ 9种来源: 覆盖所有公告类型")
print("• 📊 智能排序: 按时间倒序显示")
print("• 💡 用户友好: 清晰的数字选择界面")
print()
print("📝 使用方法:")
print("1. 点击企业微信菜单中的'最新公告'")
print("2. 系统显示9种公告来源选项")
print("3. 发送对应数字(1-9)或'全部'查看")
print("4. 系统显示该来源的最新10条公告")
print("\n✅ 最新公告功能升级完成!")
if __name__ == "__main__":
demo_latest_news_flow()
except ImportError as e:
print(f"❌ 导入失败: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"❌ 演示失败: {e}", file=sys.stderr)
sys.exit(1)
+1 -1
View File
@@ -1 +1 @@
1616302
1782404