""" 企业微信消息处理器 处理用户消息和事件,实现菜单功能 """ import time import json from typing import Optional, Dict, Any, List from datetime import datetime try: from ..core.config_manager import get_config from ..core.logger import get_logger from ..notification.wechat import send_system_notification from ..storage.postgresql import save_all_announcements_by_source_to_storage from ..storage.md_generator import generate_onu_md from ..core.models import Announcement except ImportError: try: from core.config_manager import get_config from core.logger import get_logger from notification.wechat import send_system_notification from storage.postgresql import save_all_announcements_by_source_to_storage from storage.md_generator import generate_onu_md from core.models import Announcement except ImportError as e: raise ImportError(f"消息处理器导入失败: {e}") logger = get_logger(__name__) class WeChatMessageHandler: """企业微信消息处理器""" def __init__(self): 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": { "key": "crawl_now", "name": "立即搜索", "description": "立即执行一次公告搜索" }, "today_stats": { "key": "today_stats", "name": "今日统计", "description": "查看今日公告统计信息" }, "keyword_search": { "key": "keyword_search", "name": "关键词搜索", "description": "输入关键词搜索公告" }, "latest_news": { "key": "latest_news", "name": "最新公告", "description": "查看最新发布的公告" }, "keyword_manage": { "key": "keyword_manage", "name": "关键词管理", "description": "管理监控关键词" }, "system_status": { "key": "system_status", "name": "系统状态", "description": "查看系统运行状态" }, "clear_cache": { "key": "clear_cache", "name": "清理缓存", "description": "清理系统缓存数据" }, "help_guide": { "key": "help_guide", "name": "使用说明", "description": "查看详细使用说明" } } logger.info("企业微信消息处理器初始化完成") def _get_monitor_app(self): """获取监控应用实例""" if self.monitor_app is None: try: from ..main import GXGPMonitorApp self.monitor_app = GXGPMonitorApp() if not self.monitor_app.initialize(): logger.error("监控应用初始化失败") return None except ImportError: logger.error("无法导入监控应用") return None return self.monitor_app def handle_event(self, event: str, event_key: Optional[str], from_user: str) -> Optional[str]: """处理事件消息""" try: logger.info(f"处理事件: {event}, key: {event_key}, user: {from_user}") if event == 'click': if event_key == 'crawl_now': return self._handle_crawl_now(from_user) elif event_key == 'today_stats': return self._handle_today_stats(from_user) elif event_key == 'keyword_search': return self._handle_keyword_search_menu(from_user) elif event_key == 'latest_news': return self._handle_latest_news(from_user) elif event_key == 'keyword_manage': return self._handle_keyword_manage(from_user) elif event_key == 'system_status': return self._handle_system_status(from_user) elif event_key == 'clear_cache': return self._handle_clear_cache(from_user) elif event_key == 'help_guide': return self._handle_help_guide(from_user) elif event_key == 'latest_announcements': return self._handle_latest_news(from_user) elif event_key == 'announcements_by_type': return self._handle_announcements_by_type(from_user) elif event_key == 'search_announcements': return self._handle_keyword_search_menu(from_user) else: return self._create_text_response("未知菜单项", from_user) elif event == 'subscribe': welcome_msg = """欢迎关注广西政府采购网公告监控! 我可以帮您: - 自动监控最新采购公告 - 筛选您关心的关键词信息 - 及时推送重要更新 点击下方菜单开始使用.""" return self._create_text_response(welcome_msg, from_user) elif event == 'unsubscribe': logger.info(f"用户 {from_user} 取消关注") return None else: logger.info(f"未处理的event类型: {event}") return None except Exception as e: logger.error(f"事件处理异常: {str(e)}") return self._create_text_response("处理失败,请稍后重试", from_user) def handle_text_message(self, content: str, from_user: str) -> Optional[str]: """处理文本消息""" try: logger.info(f"处理文本消息: {content}, user: {from_user}") 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("搜索"): return self._handle_manual_crawl(content, from_user) elif content.startswith("总结") or content == "统计": return self._handle_today_stats(from_user) elif content.startswith("最新公告") or content.startswith("最新"): return self._handle_latest_news(from_user) elif content.startswith("系统状态") or content.startswith("状态"): return self._handle_system_status(from_user) elif content.startswith("关键词"): return self._handle_keyword_search(content, from_user) elif content.startswith("添加关键词"): return self._create_text_response("关键词管理功能正在开发中,请联系管理员", from_user) elif content.startswith("删除关键词"): return self._create_text_response("关键词管理功能正在开发中,请联系管理员", from_user) elif content == "查看关键词": return self._create_text_response("当前监控关键词: 政府采购、大化、南宁、信息化", from_user) elif content.startswith("清理缓存") or content.startswith("清理"): 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) except Exception as e: logger.error(f"文本消息处理异常: {str(e)}") return self._create_text_response("处理失败,请稍后重试", from_user) def _handle_crawl_now(self, from_user: str) -> Optional[str]: """处理立即搜索菜单""" try: logger.info(f"用户 {from_user} 触发立即搜索") app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) result = app.run_crawl() if result.get("success"): total = result.get("total_crawled", 0) filtered = result.get("filtered", 0) saved = result.get("saved", 0) response = f"""搜索完成! 统计信息: - 总共发现: {total} 条公告 - 关键词筛选: {filtered} 条 - 已保存: {saved} 条 如有匹配的公告,我会及时推送通知.""" else: error = result.get("error", "未知错误") 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 _handle_today_stats(self, from_user: str) -> Optional[str]: """处理今日统计菜单""" try: logger.info(f"用户 {from_user} 请求今日统计") app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) try: from ..storage.postgresql import get_storage_manager storage = get_storage_manager() stats = storage.get_statistics() if stats: response = f"""今日公告统计 统计时间: {datetime.now().strftime('%Y-%m-%d %H:%M')} 数据概览: - 今日新增: {stats.get('today_count', 0)} 条 - 累计总数: {stats.get('total_count', 0)} 条 - 活跃关键词: {stats.get('active_keywords', 0)} 个 分类统计: - 采购公告: {stats.get('purchase_count', 0)} 条 - 结果公告: {stats.get('result_count', 0)} 条 - 更正公告: {stats.get('correction_count', 0)} 条 - 其他类型: {stats.get('other_count', 0)} 条 提示: 数据每小时更新,点击"立即搜索"可获取最新数据.""" else: response = """今日公告统计 暂无统计数据,请先执行"立即搜索"获取最新公告。 建议: 系统会在每日定时搜索,您也可以手动触发更新.""" except Exception as db_error: logger.warning(f"数据库查询失败,使用默认统计: {str(db_error)}") response = """系统状态 数据库连接中,请稍后查看详细统计。 您可以: - 点击"立即搜索"更新数据 - 查看"系统状态"了解服务运行情况""" 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_keyword_search_menu(self, from_user: str) -> Optional[str]: """处理关键词搜索菜单""" try: logger.info(f"用户 {from_user} 触发关键词搜索菜单") response = """关键词搜索 请直接发送您想要搜索的关键词,我将为您查找相关的公告信息。 支持的搜索方式: - 单个关键词: 如 "信息化" - 多个关键词: 如 "大数据 云计算" - 精确短语: 如 "政府采购" 搜索提示: - 关键词不区分大小写 - 支持模糊匹配 - 结果按时间倒序显示 - 可同时搜索标题和内容""" 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(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条 🕒 更新时间:{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 "未知" 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 += "💡 发送关键词可搜索相关公告,点击菜单可查看更多功能。" else: response = """📋 全部公告 暂无最新公告数据。 建议: - 点击"立即搜索"更新数据 - 检查系统状态确保服务正常""" except Exception as db_error: logger.warning(f"数据库查询失败: {str(db_error)}") response = """📋 全部公告 暂时无法获取数据,请稍后重试。 您可以先尝试"立即搜索"更新数据。""" 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_keyword_manage(self, from_user: str) -> Optional[str]: """处理关键词管理菜单""" try: logger.info(f"用户 {from_user} 请求关键词管理") # 尝试从配置中获取关键词 current_keywords = [] try: if hasattr(self.config, 'crawler') and self.config.crawler and hasattr(self.config.crawler, 'keyword'): current_keywords = self.config.crawler.keyword else: # 从配置文件直接读取 import yaml import os config_path = os.path.join(os.path.dirname(__file__), '..', '..', 'gx_gp_monitor', 'config', 'config.yaml') if os.path.exists(config_path): with open(config_path, 'r', encoding='utf-8') as f: config_data = yaml.safe_load(f) current_keywords = config_data.get('crawler', {}).get('keyword', []) except Exception as e: logger.warning(f"读取关键词配置失败: {str(e)}") # 如果获取不到,使用默认显示 if not current_keywords: current_keywords = ["大化", "南宁", "信息化", "政府采购"] keywords_str = "\n• ".join(current_keywords) response = f"""🔍 系统当前关键词 📋 正在监控的关键词: • {keywords_str} 💡 关键词说明: • 系统会自动监控包含这些关键词的公告 • 匹配时会立即推送通知 • 关键词支持模糊匹配 ⚙️ 关键词管理: 如需添加或修改关键词,请联系系统管理员。""" 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_system_status(self, from_user: str) -> Optional[str]: """处理系统状态菜单""" try: logger.info(f"用户 {from_user} 请求系统状态") status_info = { "database": "检查中...", "crawler": "检查中...", "wechat": "检查中...", "scheduler": "检查中..." } try: # 确保数据库已初始化 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]}..." try: app = self._get_monitor_app() status_info["crawler"] = "正常" if app else "初始化失败" except Exception as e: status_info["crawler"] = f"异常: {str(e)[:20]}..." try: from ..notification.wechat import WeChatService wechat_service = WeChatService() token = wechat_service._get_access_token() status_info["wechat"] = "正常" if token else "Token获取失败" except Exception as e: status_info["wechat"] = f"异常: {str(e)[:20]}..." status_info["scheduler"] = "运行中" response = f"""系统状态报告 检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 组件状态: - 数据库: {status_info['database']} - 爬虫服务: {status_info['crawler']} - 微信服务: {status_info['wechat']} - 调度器: {status_info['scheduler']} """ 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_clear_cache(self, from_user: str) -> Optional[str]: """处理清理缓存菜单""" try: logger.info(f"用户 {from_user} 请求清理缓存") cache_cleared = { "database_cache": False, "file_cache": False, "memory_cache": False } try: # 目前没有专门的数据库缓存清理功能 # 可以在这里添加数据库维护逻辑,比如清理过期数据 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 import shutil cache_cleared["file_cache"] = True except Exception as e: logger.warning(f"文件缓存清理失败: {str(e)}") try: if hasattr(self, '_cache'): self._cache.clear() cache_cleared["memory_cache"] = True except Exception as e: logger.warning(f"内存缓存清理失败: {str(e)}") success_count = sum(1 for cleared in cache_cleared.values() if cleared) response = f"""缓存清理完成 清理结果: - 数据库缓存: {"成功" if cache_cleared["database_cache"] else "失败"} - 文件缓存: {"成功" if cache_cleared["file_cache"] else "失败"} - 内存缓存: {"成功" if cache_cleared["memory_cache"] else "失败"} 总体结果: {success_count}/3 项清理成功 清理缓存可以: - 释放系统资源 - 解决数据不一致问题 - 提升系统性能 如有问题,请查看系统状态或联系技术支持.""" 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_help_guide(self, from_user: str) -> Optional[str]: """处理使用说明菜单""" try: logger.info(f"用户 {from_user} 请求使用说明") help_text = """菜单功能详解: 监控操作: - 立即搜索: 手动触发公告搜索,获取最新数据 - 今日统计: 查看今日公告统计信息和数据概览 - 关键词搜索: 输入关键词搜索相关公告 - 最新公告: 浏览最近发布的10条公告 系统管理: - 关键词管理: 管理监控关键词(需管理员权限) - 系统状态: 查看各组件运行状态 - 清理缓存: 清理系统缓存,提升性能 帮助: - 使用说明: 查看详细功能介绍 文本命令: - 发送关键词直接搜索 - "搜索 [关键词]" 指定关键词搜索 - "总结" 查看今日统计 - "帮助" 显示此说明 智能推送: 系统会自动监控匹配关键词的公告,并通过企业微信实时推送。 使用技巧: - 关键词支持中英文混合 - 可同时搜索多个关键词 - 公告按时间倒序排列 - 点击公告可查看详情 """ return self._create_text_response(help_text, from_user) except Exception as e: logger.error(f"使用说明处理异常: {str(e)}") return self._create_text_response("获取帮助信息失败,请稍后重试", from_user) def _handle_announcements_by_type(self, from_user: str) -> Optional[str]: """处理按类型查看公告菜单""" try: logger.info(f"用户 {from_user} 请求按类型查看公告") try: from ..storage.postgresql import get_storage_manager storage = get_storage_manager() all_stats = storage.get_statistics() type_stats = all_stats.get('announcement_types', {}) if type_stats: response = f"""公告类型统计 统计时间: {datetime.now().strftime('%Y-%m-%d %H:%M')} 各类型公告数量: """ type_name_map = { 'purchase': '采购公告', 'result': '结果公告', 'correction': '更正公告', 'contract': '合同公告', 'pre_announcement': '预公示', 'single_source': '单一来源', 'electronic_market': '电子卖场', 'acceptance': '履约验收', 'engineering': '工程公告' } for ann_type, count in type_stats.items(): type_name = type_name_map.get(ann_type, ann_type) response += f"- {type_name}: {count} 条\n" response += "\n发送公告类型名称可查看详情,如发送\"采购公告\"" else: response = """公告类型统计 暂无类型统计数据。 建议: - 点击"立即搜索"更新数据 - 系统将自动分类统计各种公告""" except Exception as db_error: logger.warning(f"数据库查询失败: {str(db_error)}") response = """公告类型说明 系统支持以下类型的政府采购公告: - 采购公告: 招标、采购等采购信息 - 结果公告: 中标、成交等结果信息 - 更正公告: 变更、澄清等修改信息 - 合同公告: 合同签订等信息 - 预公示: 招标文件预公示 - 单一来源: 单一来源采购公示 - 电子卖场: 电子化采购平台 - 履约验收: 项目验收信息 - 工程公告: 工程建设相关 发送具体类型名称可搜索相关公告。""" 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_search_by_type(self, type_name: str, from_user: str) -> Optional[str]: """处理按类型搜索公告""" try: logger.info(f"用户 {from_user} 按类型搜索公告: {type_name}") type_mapping = { "采购公告": "purchase", "结果公告": "result", "更正公告": "correction", "合同公告": "contract", "预公示": "pre_announcement", "单一来源": "single_source", "电子卖场": "electronic_market", "履约验收": "acceptance", "工程公告": "engineering" } ann_type = type_mapping.get(type_name) if not ann_type: return self._create_text_response(f"未知的公告类型: {type_name}", from_user) try: from ..storage.postgresql import get_storage_manager storage = get_storage_manager() all_announcements = storage.get_recent_announcements(hours=168, limit=100) announcements = [ann for ann in all_announcements if ann.announcement_type.value == ann_type][:5] if announcements: response = f"""{type_name} (最近5条) 更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M')} """ for i, announcement in enumerate(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_manual_crawl(self, content: str, from_user: str) -> Optional[str]: """处理手动搜索命令""" try: parts = content.split() if len(parts) < 2: return self._create_text_response("请指定搜索关键词,例如: 搜索 大化", from_user) keywords = parts[1:] logger.info(f"用户 {from_user} 手动搜索关键词: {keywords}") app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) result = app.run_crawl(keywords=keywords, manual_crawl=True) if result.get("success"): total = result.get("total_crawled", 0) filtered = result.get("filtered", 0) if filtered > 0: response = f"""搜索完成! 关键词: {' '.join(keywords)} 发现匹配公告: {filtered} 条 如有匹配的公告,我会及时推送通知。""" else: response = f"""搜索完成! 关键词: {' '.join(keywords)} 未发现匹配的公告。 建议: - 尝试更通用的关键词 - 检查关键词拼写 - 等待系统更新最新数据""" else: error = result.get("error", "未知错误") 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 _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 logger.info(f"用户 {from_user} 关键词搜索: {keywords}") if not keywords: return self._create_text_response("请提供搜索关键词", from_user) app = self._get_monitor_app() if not app: return self._create_text_response("系统初始化失败,请稍后重试", from_user) result = app.run_crawl(keywords=keywords, manual_crawl=True) if result.get("success"): filtered_announcements = result.get("filtered_announcements", []) if filtered_announcements: # 生成markdown格式的结果 markdown_content = self._generate_keyword_search_markdown(keywords, filtered_announcements) # 发送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"""🔍 搜索完成 关键词: {' '.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) 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: logger.info(f"处理其他消息类型: {msg_type}, user: {from_user}") if msg_type == 'image': return self._create_text_response("收到图片消息,但我只能处理文本消息", from_user) elif msg_type == 'voice': return self._create_text_response("收到语音消息,但我只能处理文本消息", from_user) else: return self._create_text_response(f"收到{msg_type}消息,暂不支持此类型", from_user) except Exception as e: 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())) response_xml = f""" {timestamp} """ return response_xml