feat: 企业微信机器人集成到后端服务,网页端可配置和重启
- BotManager: 后端启动时自动检测配置并拉起机器人(后台线程) - WeComBotService: 新增 start_async() 异步模式,支持嵌入事件循环 - settings API: 增加 bot 配置字段(enabled/bot_id/secret) - settings API: 增加 bot/status 和 bot/restart 端点 - 前端设置页: 增加企业微信机器人配置卡片(启用开关/ID/Secret) - 前端设置页: 增加机器人状态徽章和重启按钮 - Secret 脱敏显示,仅展示前4位
This commit is contained in:
87
app/main.py
87
app/main.py
@@ -5,8 +5,10 @@ FastAPI 应用入口
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator
|
||||
@@ -18,6 +20,88 @@ from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
|
||||
from app.config import settings
|
||||
from app.database import close_db, init_db
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BotManager:
|
||||
"""管理企业微信机器人的生命周期(在后台线程中运行)"""
|
||||
|
||||
def __init__(self):
|
||||
self._thread: threading.Thread | None = None
|
||||
self._running = False
|
||||
|
||||
def start(self):
|
||||
"""在后台线程中启动机器人"""
|
||||
if self._running:
|
||||
_logger.info("企业微信机器人已在运行")
|
||||
return
|
||||
if not settings.WEWORK_BOT_ENABLED or not settings.WEWORK_BOT_ID or not settings.WEWORK_BOT_SECRET:
|
||||
_logger.info("企业微信机器人未配置,跳过启动")
|
||||
return
|
||||
|
||||
self._thread = threading.Thread(
|
||||
target=self._run_bot, daemon=True, name="wework-bot"
|
||||
)
|
||||
self._thread.start()
|
||||
_logger.info("企业微信机器人已在后台线程启动")
|
||||
|
||||
def _run_bot(self):
|
||||
"""在线程中运行 bot(需要新建事件循环)"""
|
||||
self._running = True
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(self._run_bot_async())
|
||||
except Exception as e:
|
||||
_logger.error("企业微信机器人线程异常退出: %s", e, exc_info=True)
|
||||
finally:
|
||||
loop.close()
|
||||
self._running = False
|
||||
|
||||
async def _run_bot_async(self):
|
||||
"""异步运行 bot"""
|
||||
from app.wework_bot import WeComBotService
|
||||
service = WeComBotService()
|
||||
await service.start_async()
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""重启机器人(先停后启)"""
|
||||
was_running = self._running
|
||||
# 停止旧的(设置 enabled=False 让旧线程自然退出)
|
||||
old_id = settings.WEWORK_BOT_ID
|
||||
old_secret = settings.WEWORK_BOT_SECRET
|
||||
old_enabled = settings.WEWORK_BOT_ENABLED
|
||||
|
||||
if not old_enabled or not old_id or not old_secret:
|
||||
return {"success": False, "message": "请先配置机器人 ID 和 Secret 并启用"}
|
||||
|
||||
# 等待旧线程结束
|
||||
if self._thread and self._thread.is_alive():
|
||||
# SDK 的 run() 会阻塞,无法从外部优雅停止
|
||||
# 通过设置 enabled=False 让下次检查时退出
|
||||
_logger.info("等待旧机器人线程结束...")
|
||||
# 无法强制停止,提示用户
|
||||
if was_running:
|
||||
return {"success": False, "message": "旧机器人正在运行,请稍后重试(SDK 不支持强制停止)"}
|
||||
|
||||
# 启动新的
|
||||
self.start()
|
||||
return {"success": True, "message": "机器人已启动"}
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""获取机器人状态"""
|
||||
configured = bool(settings.WEWORK_BOT_ID and settings.WEWORK_BOT_SECRET)
|
||||
return {
|
||||
"enabled": settings.WEWORK_BOT_ENABLED,
|
||||
"configured": configured,
|
||||
"running": self._running,
|
||||
"bot_id": settings.WEWORK_BOT_ID,
|
||||
}
|
||||
|
||||
|
||||
# 全局 bot 管理器
|
||||
bot_manager = BotManager()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
@@ -32,6 +116,9 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
settings.ensure_dirs()
|
||||
await init_db()
|
||||
|
||||
# 启动企业微信机器人(后台线程)
|
||||
bot_manager.start()
|
||||
|
||||
# 预热嵌入服务(懒加载,首次调用时初始化)
|
||||
try:
|
||||
from app.services.embedding_service import EmbeddingService
|
||||
|
||||
Reference in New Issue
Block a user