#!/usr/bin/env python3 """ 企业微信会话存档API路由 """ from typing import Optional from fastapi import APIRouter, HTTPException, Query, Depends, BackgroundTasks from fastapi.responses import FileResponse, JSONResponse import logging from app.services.wework_service import get_wework_service, WeworkService logger = logging.getLogger(__name__) router = APIRouter( prefix="/wework", tags=["wework"], responses={404: {"description": "Not found"}}, ) def get_service(): """依赖注入:获取企业微信服务实例""" return get_wework_service() @router.get("/health") async def health_check(service: WeworkService = Depends(get_service)): """健康检查端点""" return { "status": "healthy", "service": "wework", "corpid": service.corpid, "media_dir": str(service.media_dir) } @router.get("/chat") async def get_chat( seq: int = Query(0, description="从指定的seq开始拉取消息,首次使用请使用0"), limit: int = Query(100, description="一次拉取的消息条数,最大值1000条", ge=1, le=1000), proxy: Optional[str] = Query(None, description="代理链接,如:socks5://10.0.0.1:8081 或 http://10.0.0.1:8081"), passwd: Optional[str] = Query(None, description="代理账号密码,如:user_name:passwd_123"), timeout: int = Query(5, description="超时时间,单位秒", ge=1, le=60), service: WeworkService = Depends(get_service) ): """ 拉取会话存档 从企业微信拉取聊天记录,支持代理和分页 """ try: result = service.get_chat_data( seq=seq, limit=limit, proxy=proxy or "", passwd=passwd or "", timeout=timeout ) return result except Exception as e: logger.error(f"拉取会话存档失败: {e}") raise HTTPException(status_code=500, detail=f"拉取会话存档失败: {str(e)}") @router.post("/media") async def download_media( fileid: str = Query(..., description="媒体文件id,从解密后的会话存档中得到"), filename: Optional[str] = Query(None, description="保存的文件名,如果为空则自动生成"), proxy: Optional[str] = Query(None, description="代理链接"), passwd: Optional[str] = Query(None, description="代理账号密码"), timeout: int = Query(5, description="超时时间,单位秒", ge=1, le=60), background_tasks: BackgroundTasks = None, service: WeworkService = Depends(get_service) ): """ 拉取媒体文件 下载企业微信中的媒体文件(图片、语音、视频等),支持大文件分片下载 """ try: result = service.get_media_data( sdk_fileid=fileid, filename=filename, proxy=proxy or "", passwd=passwd or "", timeout=timeout ) if result.get("success"): # 可以添加后台任务,比如发送下载完成通知等 if background_tasks: background_tasks.add_task( logger.info, f"媒体文件下载完成: {result['filename']}, 大小: {result['filesize']} 字节" ) return result else: raise HTTPException(status_code=500, detail=result.get("error", "下载失败")) except Exception as e: logger.error(f"拉取媒体文件失败: {e}") raise HTTPException(status_code=500, detail=f"拉取媒体文件失败: {str(e)}") @router.get("/media/files") async def list_media_files( service: WeworkService = Depends(get_service) ): """ 获取已下载的媒体文件列表 返回所有已下载的媒体文件信息 """ try: files = service.get_media_files() return { "count": len(files), "files": files } except Exception as e: logger.error(f"获取媒体文件列表失败: {e}") raise HTTPException(status_code=500, detail=f"获取媒体文件列表失败: {str(e)}") @router.get("/media/download/{filename}") async def download_file( filename: str, service: WeworkService = Depends(get_service) ): """ 下载媒体文件 通过文件名下载已保存的媒体文件 """ try: file_path = service.media_dir / filename if not file_path.exists(): raise HTTPException(status_code=404, detail="文件不存在") return FileResponse( path=file_path, filename=filename, media_type='application/octet-stream' ) except HTTPException: raise except Exception as e: logger.error(f"下载文件失败: {e}") raise HTTPException(status_code=500, detail=f"下载文件失败: {str(e)}") @router.delete("/media/cleanup") async def cleanup_media_files( days: int = Query(7, description="保留天数,早于此天数的文件将被删除", ge=1, le=365), service: WeworkService = Depends(get_service) ): """ 清理旧的媒体文件 删除指定天数之前的媒体文件 """ try: deleted_count = service.cleanup_old_files(days) return { "message": f"成功删除 {deleted_count} 个旧文件", "deleted_count": deleted_count, "days": days } except Exception as e: logger.error(f"清理媒体文件失败: {e}") raise HTTPException(status_code=500, detail=f"清理媒体文件失败: {str(e)}") @router.post("/decrypt") async def decrypt_message( encrypt_key: str = Query(..., description="解密密钥,使用RSA私钥解密encrypt_random_key后得到"), encrypt_msg: str = Query(..., description="加密的消息内容"), service: WeworkService = Depends(get_service) ): """ 解密会话内容 使用企业自持的RSA私钥解密后的encrypt_key来解密encrypt_chat_msg """ try: result = service.decrypt_data(encrypt_key, encrypt_msg) return result except Exception as e: logger.error(f"解密消息失败: {e}") raise HTTPException(status_code=500, detail=f"解密消息失败: {str(e)}") @router.get("/config") async def get_config( service: WeworkService = Depends(get_service) ): """ 获取当前配置 返回企业微信服务的配置信息 """ return { "corpid": service.corpid, "corpid_masked": service.corpid[:8] + "..." + service.corpid[-4:] if len(service.corpid) > 12 else service.corpid, "media_dir": str(service.media_dir), "media_dir_exists": service.media_dir.exists() } # 应用关闭时的清理 @router.on_event("shutdown") async def shutdown_event(): """应用关闭时清理资源""" from app.services.wework_service import close_wework_service close_wework_service() logger.info("企业微信服务已关闭")