feat: Step 3 - Bot API 路由 (v0.9.2)

- 创建 bots.py 路由文件
- 实现 9 个 REST API 端点:
  - GET /api/bots - 获取列表
  - POST /api/bots - 创建
  - GET /api/bots/:id - 详情
  - PUT /api/bots/:id - 更新
  - DELETE /api/bots/:id - 删除
  - POST /api/bots/:id/bind - 绑定 Agent
  - POST /api/bots/:id/unbind - 解绑 Agent
  - GET /api/bots/:id/status - 状态
  - POST /api/bots/:id/heartbeat - 心跳
  - POST /api/bots/:id/token - 重新生成 Token
  - GET /api/bots/:id/stats - 统计
- 实现 JWT 权限检查
- 实现 X-Bot-Token 认证
- 注册蓝图到应用
- 更新版本号到 0.9.2
This commit is contained in:
2026-03-15 10:28:57 +08:00
parent 1ba9f78bd8
commit 608e53ed2f
2 changed files with 365 additions and 1 deletions

362
app/routes/bots.py Normal file
View File

@@ -0,0 +1,362 @@
"""
Bot 路由 - 机器人管理 API
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import datetime
from app.models import User, Agent
from app.services.bot_service import BotService
bots_bp = Blueprint('bots', __name__)
@bots_bp.route('/', methods=['GET'])
@jwt_required()
def get_bots():
"""
获取机器人列表
Query params:
- owner_only: bool - 只返回自己的 Bot
- include_system: bool - 包含系统 Bot
"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
owner_only = request.args.get('owner_only', 'false').lower() == 'true'
if owner_only:
bots = BotService.get_bots_by_owner(user_id)
else:
bots = BotService.get_available_bots(user)
return jsonify({
'bots': [bot.to_dict() for bot in bots],
'count': len(bots)
}), 200
@bots_bp.route('/', methods=['POST'])
@jwt_required()
def create_bot():
"""
创建机器人
Request body:
- name: str - 机器人名称(必填,唯一)
- display_name: str - 显示名称
- avatar: str - 头像 URL
- description: str - 描述
- is_system: bool - 是否为系统级 Bot仅 admin
- agent_id: str - 绑定的 Agent ID
- capabilities: list - 能力标签
- config: dict - 配置
"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
data = request.get_json()
# 验证必填字段
if not data or 'name' not in data:
return jsonify({'error': 'name is required'}), 400
name = data['name'].strip()
# 检查名称是否已存在
if BotService.get_bot_by_name(name):
return jsonify({'error': 'Bot name already exists'}), 400
# 检查 is_system 权限
is_system = data.get('is_system', False)
if is_system and user.role != 'admin':
return jsonify({'error': 'Only admin can create system bots'}), 403
# 检查 agent_id 是否有效
agent_id = data.get('agent_id')
if agent_id:
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
# 创建 Bot
bot, token = BotService.create_bot(
name=name,
owner_id=user_id,
display_name=data.get('display_name'),
avatar=data.get('avatar'),
description=data.get('description'),
is_system=is_system,
agent_id=agent_id,
capabilities=data.get('capabilities'),
config=data.get('config')
)
return jsonify({
'bot': bot.to_dict(),
'token': token # 只在创建时返回一次
}), 201
@bots_bp.route('/<bot_id>', methods=['GET'])
@jwt_required()
def get_bot(bot_id):
"""获取机器人详情"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'view'):
return jsonify({'error': 'Permission denied'}), 403
return jsonify({'bot': bot.to_dict()}), 200
@bots_bp.route('/<bot_id>', methods=['PUT'])
@jwt_required()
def update_bot(bot_id):
"""
更新机器人配置
Request body:
- display_name: str
- avatar: str
- description: str
- capabilities: list
- config: dict
"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'edit'):
return jsonify({'error': 'Permission denied'}), 403
data = request.get_json() or {}
bot = BotService.update_bot(
bot=bot,
display_name=data.get('display_name'),
avatar=data.get('avatar'),
description=data.get('description'),
capabilities=data.get('capabilities'),
config=data.get('config')
)
return jsonify({'bot': bot.to_dict()}), 200
@bots_bp.route('/<bot_id>', methods=['DELETE'])
@jwt_required()
def delete_bot(bot_id):
"""删除机器人"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'delete'):
return jsonify({'error': 'Permission denied'}), 403
# 不允许删除系统 Bot除非是 admin
if bot.is_system and user.role != 'admin':
return jsonify({'error': 'Cannot delete system bot'}), 403
success = BotService.delete_bot(bot)
if success:
return jsonify({'message': 'Bot deleted'}), 200
else:
return jsonify({'error': 'Failed to delete bot'}), 500
@bots_bp.route('/<bot_id>/bind', methods=['POST'])
@jwt_required()
def bind_agent(bot_id):
"""
绑定 Agent
Request body:
- agent_id: str - Agent ID
"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'bind'):
return jsonify({'error': 'Permission denied'}), 403
data = request.get_json()
if not data or 'agent_id' not in data:
return jsonify({'error': 'agent_id is required'}), 400
agent_id = data['agent_id']
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
bot = BotService.bind_agent(bot, agent_id)
return jsonify({
'bot': bot.to_dict(),
'message': f'Bot bound to agent {agent.name}'
}), 200
@bots_bp.route('/<bot_id>/unbind', methods=['POST'])
@jwt_required()
def unbind_agent(bot_id):
"""解绑 Agent"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'bind'):
return jsonify({'error': 'Permission denied'}), 403
bot = BotService.bind_agent(bot, None)
return jsonify({
'bot': bot.to_dict(),
'message': 'Bot unbound from agent'
}), 200
@bots_bp.route('/<bot_id>/status', methods=['GET'])
@jwt_required()
def get_bot_status(bot_id):
"""获取机器人状态"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'view'):
return jsonify({'error': 'Permission denied'}), 403
return jsonify({
'bot_id': bot.id,
'name': bot.name,
'status': bot.status,
'agent_id': bot.agent_id,
'last_active_at': bot.last_active_at.isoformat() if bot.last_active_at else None
}), 200
@bots_bp.route('/<bot_id>/heartbeat', methods=['POST'])
def bot_heartbeat(bot_id):
"""
机器人心跳上报
用于 Bot Token 认证(非 JWT
Headers: X-Bot-Token: <token>
"""
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# Token 认证
token = request.headers.get('X-Bot-Token')
if not token or not BotService.verify_token(bot, token):
return jsonify({'error': 'Invalid bot token'}), 401
# 更新状态
BotService.update_status(bot, 'online')
return jsonify({'status': 'ok'}), 200
@bots_bp.route('/<bot_id>/token', methods=['POST'])
@jwt_required()
def regenerate_token(bot_id):
"""重新生成 Bot Token"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'edit'):
return jsonify({'error': 'Permission denied'}), 403
token = BotService.regenerate_token(bot)
return jsonify({
'message': 'Token regenerated',
'token': token
}), 200
@bots_bp.route('/<bot_id>/stats', methods=['GET'])
@jwt_required()
def get_bot_stats(bot_id):
"""获取机器人统计信息"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
bot = BotService.get_bot_by_id(bot_id)
if not bot:
return jsonify({'error': 'Bot not found'}), 404
# 权限检查
if not BotService.check_permission(user, bot, 'view'):
return jsonify({'error': 'Permission denied'}), 403
stats = BotService.get_bot_stats(bot)
return jsonify(stats), 200