Files
pit-router/app/services/bot_service.py
feifei.xu 1ba9f78bd8 feat: Step 2 - Bot 服务层 (v0.9.1)
- 创建 BotService 服务类
- 实现 CRUD 操作
- 实现 Agent 绑定/解绑
- 实现权限检查 (check_permission)
- 实现 Token 生成/验证/重新生成
- 实现状态同步 (sync_agent_status)
- 实现统计信息获取
- 更新 services/__init__.py 导出
2026-03-15 10:23:41 +08:00

278 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 IDNone 表示解绑
"""
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,
}