feat: Step 2 - Bot 服务层 (v0.9.1)

- 创建 BotService 服务类
- 实现 CRUD 操作
- 实现 Agent 绑定/解绑
- 实现权限检查 (check_permission)
- 实现 Token 生成/验证/重新生成
- 实现状态同步 (sync_agent_status)
- 实现统计信息获取
- 更新 services/__init__.py 导出
This commit is contained in:
2026-03-15 10:23:41 +08:00
parent 8673eaf655
commit 1ba9f78bd8
2 changed files with 279 additions and 0 deletions

View File

@@ -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',
]

277
app/services/bot_service.py Normal file
View File

@@ -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 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,
}