import logging import xml.etree.ElementTree as ET from app.config import settings from app.wechat.crypto import WXBizMsgCrypt from app.wechat.client import WeChatClient logger = logging.getLogger(__name__) class WeChatMessageHandler: def __init__(self): self.wxcpt = WXBizMsgCrypt( sToken=settings.wechat_token, sEncodingAESKey=settings.wechat_encoding_aes_key, sReceiveId=settings.wechat_corp_id, ) self.client = WeChatClient() def verify_url( self, msg_signature: str, timestamp: str, nonce: str, echostr: str ) -> str | None: ret, sEchoStr = self.wxcpt.VerifyURL( msg_signature, timestamp, nonce, echostr ) if ret == 0: return ( sEchoStr.decode("utf-8") if isinstance(sEchoStr, bytes) else sEchoStr ) return None def decrypt_message( self, post_data: str, msg_signature: str, timestamp: str, nonce: str, ) -> ET.Element | None: ret, xml_content = self.wxcpt.DecryptMsg( post_data, msg_signature, timestamp, nonce ) if ret != 0: return None return ET.fromstring(xml_content) def encrypt_response( self, response_xml: str, nonce: str, timestamp: str ) -> str | None: ret, encrypted = self.wxcpt.EncryptMsg(response_xml, nonce, timestamp) if ret == 0: return encrypted return None async def handle_event( self, event: str, event_key: str | None, from_user: str ) -> str | None: if event != "click" or not event_key: return None if event_key == "today_stats": return await self._handle_today_stats(from_user) elif event_key == "trigger_crawl": return await self._handle_trigger_crawl(from_user) elif event_key == "sync_holidays": return await self._handle_sync_holidays(from_user) return None async def handle_text(self, content: str, from_user: str) -> str | None: return None async def _handle_today_stats(self, from_user: str) -> str | None: from app.api.deps import get_db try: async for db in get_db(): from sqlalchemy import func, select from app.models.announcement import Announcement total_result = await db.execute( select(func.count()).select_from(Announcement) ) total = total_result.scalar() or 0 today_result = await db.execute( select(func.count()).where( func.date(Announcement.publish_date) == func.current_date() ).select_from(Announcement) ) today = today_result.scalar() or 0 text = f"今日新增: {today} 条\n累计公告: {total} 条" await self.client.send_text(text, from_user) except Exception as e: logger.error(f"查询统计失败: {e}") await self.client.send_text("查询失败,请稍后再试", from_user) async def _handle_trigger_crawl(self, from_user: str) -> str | None: from app.api.deps import get_crawl_service await self.client.send_text("开始爬取,请稍候...", from_user) try: service = get_crawl_service() results = await service.run_all() total = sum(r.total_count for r in results) stored = sum( r.pipeline_result.stored for r in results if r.pipeline_result ) notified = sum( r.pipeline_result.notified for r in results if r.pipeline_result ) errors = [r.error_message for r in results if not r.success] msg = f"爬取完成\n抓取: {total} 条\n新增: {stored} 条\n推送: {notified} 条" if errors: msg += f"\n异常: {errors[0][:50]}" await self.client.send_text(msg, from_user) except Exception as e: logger.error(f"手动爬取失败: {e}") await self.client.send_text(f"爬取失败: {e}", from_user) async def _handle_sync_holidays(self, from_user: str) -> str | None: from app.services.holiday_service import now_in_china, sync_holidays from app.api.deps import get_db try: async for db in get_db(): year = now_in_china().year count = await sync_holidays(db, year) await self.client.send_text( f"已同步 {year} 年节假日\n共 {count} 条记录", from_user, ) except Exception as e: logger.error(f"同步节假日失败: {e}") await self.client.send_text(f"同步失败: {e}", from_user)