diff --git a/app/services/__init__.py b/app/services/__init__.py index d081b6b..3178a41 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -6,6 +6,7 @@ from .message_queue import MessageQueue from .session_service import SessionService from .message_service import MessageService from .agent_service import AgentService +from .bot_service import BotService __all__ = [ 'AgentScheduler', @@ -13,4 +14,5 @@ __all__ = [ 'SessionService', 'MessageService', 'AgentService', + 'BotService', ] diff --git a/app/services/bot_service.py b/app/services/bot_service.py new file mode 100644 index 0000000..0067b3f --- /dev/null +++ b/app/services/bot_service.py @@ -0,0 +1,277 @@ +""" +Bot 服务层 +实现 Bot 的 CRUD、权限控制、Token 管理、Agent 绑定等功能 +""" +import secrets +import hashlib +from datetime import datetime +from typing import Optional, List, Dict, Any, Tuple + +from app.extensions import db +from app.models import Bot, User, Agent + + +class BotService: + """Bot 服务类""" + + @staticmethod + def generate_token() -> str: + """生成 Bot API Token""" + return f"bot_{secrets.token_urlsafe(32)}" + + @staticmethod + def hash_token(token: str) -> str: + """Hash Token 用于存储""" + return hashlib.sha256(token.encode()).hexdigest() + + @staticmethod + def verify_token(bot: Bot, token: str) -> bool: + """验证 Token""" + if not bot.token_hash: + return False + return bot.token_hash == BotService.hash_token(token) + + @staticmethod + def check_permission(user: User, bot: Bot, action: str) -> bool: + """ + 检查用户对 Bot 的权限 + + Args: + user: 当前用户 + bot: Bot 对象 + action: 操作类型 ('view', 'use', 'edit', 'delete', 'bind') + + Returns: + 是否有权限 + """ + # 管理员拥有所有权限 + if user.role == 'admin': + return True + + # 查看/使用权限 + if action in ['view', 'use']: + # 系统级 Bot 所有用户可用 + if bot.is_system: + return True + # 自己的 Bot + return bot.owner_id == user.id + + # 编辑/删除/绑定权限 + if action in ['edit', 'delete', 'bind']: + # 只有所有者可以编辑/删除/绑定 + return bot.owner_id == user.id + + return False + + @staticmethod + def create_bot( + name: str, + owner_id: str, + display_name: Optional[str] = None, + avatar: Optional[str] = None, + description: Optional[str] = None, + is_system: bool = False, + agent_id: Optional[str] = None, + capabilities: Optional[List[str]] = None, + config: Optional[Dict[str, Any]] = None + ) -> Tuple[Bot, str]: + """ + 创建 Bot + + Returns: + (Bot 对象, 明文 Token) + """ + # 生成 Token + token = BotService.generate_token() + token_hash = BotService.hash_token(token) + + bot = Bot( + name=name, + display_name=display_name or name, + avatar=avatar, + description=description, + owner_id=owner_id, + agent_id=agent_id, + token_hash=token_hash, + is_system=is_system, + capabilities=capabilities or ['chat'], + config=config or BotService.get_default_config(), + status='offline', + created_at=datetime.utcnow() + ) + + db.session.add(bot) + db.session.commit() + + return bot, token + + @staticmethod + def get_default_config() -> Dict[str, Any]: + """获取默认 Bot 配置""" + return { + "model": "gpt-4o", + "temperature": 0.7, + "max_tokens": 4096, + "system_prompt": "", + "rate_limit": { + "requests_per_minute": 60, + "tokens_per_day": 100000 + }, + "features": { + "streaming": True, + "markdown": True, + "code_highlight": True, + "file_upload": False + }, + "context": { + "max_history": 20, + "summary_threshold": 10 + } + } + + @staticmethod + def get_bot_by_id(bot_id: str) -> Optional[Bot]: + """根据 ID 获取 Bot""" + return Bot.query.get(bot_id) + + @staticmethod + def get_bot_by_name(name: str) -> Optional[Bot]: + """根据名称获取 Bot""" + return Bot.query.filter_by(name=name).first() + + @staticmethod + def get_bots_by_owner(owner_id: str) -> List[Bot]: + """获取用户的所有 Bot""" + return Bot.query.filter_by(owner_id=owner_id).order_by(Bot.created_at.desc()).all() + + @staticmethod + def get_system_bots() -> List[Bot]: + """获取所有系统级 Bot""" + return Bot.query.filter_by(is_system=True).order_by(Bot.created_at.desc()).all() + + @staticmethod + def get_available_bots(user: User) -> List[Bot]: + """ + 获取用户可用的所有 Bot + - 自己的 Bot + - 系统级 Bot + """ + if user.role == 'admin': + return Bot.query.order_by(Bot.created_at.desc()).all() + + return Bot.query.filter( + (Bot.owner_id == user.id) | (Bot.is_system == True) + ).order_by(Bot.created_at.desc()).all() + + @staticmethod + def update_bot( + bot: Bot, + display_name: Optional[str] = None, + avatar: Optional[str] = None, + description: Optional[str] = None, + capabilities: Optional[List[str]] = None, + config: Optional[Dict[str, Any]] = None + ) -> Bot: + """更新 Bot 信息""" + if display_name is not None: + bot.display_name = display_name + if avatar is not None: + bot.avatar = avatar + if description is not None: + bot.description = description + if capabilities is not None: + bot.capabilities = capabilities + if config is not None: + bot.config = {**bot.config, **config} if bot.config else config + + db.session.commit() + return bot + + @staticmethod + def bind_agent(bot: Bot, agent_id: Optional[str]) -> Bot: + """ + 绑定/解绑 Agent + + Args: + bot: Bot 对象 + agent_id: Agent ID,None 表示解绑 + """ + bot.agent_id = agent_id + + # 更新状态 + if agent_id: + agent = Agent.query.get(agent_id) + if agent and agent.status == 'online': + bot.status = 'online' + else: + bot.status = 'offline' + else: + bot.status = 'offline' + + db.session.commit() + return bot + + @staticmethod + def regenerate_token(bot: Bot) -> str: + """重新生成 Token""" + token = BotService.generate_token() + bot.token_hash = BotService.hash_token(token) + db.session.commit() + return token + + @staticmethod + def delete_bot(bot: Bot) -> bool: + """删除 Bot""" + try: + db.session.delete(bot) + db.session.commit() + return True + except Exception as e: + db.session.rollback() + return False + + @staticmethod + def update_status(bot: Bot, status: str) -> Bot: + """更新 Bot 状态""" + bot.status = status + bot.last_active_at = datetime.utcnow() + db.session.commit() + return bot + + @staticmethod + def sync_agent_status(bot: Bot) -> Bot: + """ + 同步 Agent 状态到 Bot + 当 Agent 状态变化时调用 + """ + if bot.agent_id: + agent = Agent.query.get(bot.agent_id) + if agent: + bot.status = agent.status + else: + bot.status = 'offline' + else: + bot.status = 'offline' + + bot.last_active_at = datetime.utcnow() + db.session.commit() + return bot + + @staticmethod + def get_bot_stats(bot: Bot) -> Dict[str, Any]: + """获取 Bot 统计信息""" + from app.models import Session, Message + + session_count = Session.query.filter_by(bot_id=bot.id).count() + message_count = Message.query.filter_by(bot_id=bot.id).count() + + return { + 'bot_id': bot.id, + 'name': bot.name, + 'status': bot.status, + 'session_count': session_count, + 'message_count': message_count, + 'is_system': bot.is_system, + 'created_at': bot.created_at.isoformat() if bot.created_at else None, + 'last_active_at': bot.last_active_at.isoformat() if bot.last_active_at else None, + }