feat: Phase 1 - 核心功能实现

This commit is contained in:
2026-03-14 19:41:36 +08:00
parent 3e9f632501
commit bf245ee8cb
21 changed files with 1468 additions and 0 deletions

3
app/routes/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
"""
路由模块
"""

91
app/routes/agents.py Normal file
View File

@@ -0,0 +1,91 @@
"""
Agent 路由
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required
from datetime import datetime
from app.models import db, Agent
agents_bp = Blueprint('agents', __name__)
@agents_bp.route('/', methods=['GET'])
@jwt_required()
def get_agents():
"""获取 Agent 列表"""
agents = Agent.query.all()
return jsonify({'agents': [a.to_dict() for a in agents]}), 200
@agents_bp.route('/<agent_id>', methods=['GET'])
@jwt_required()
def get_agent(agent_id):
"""获取 Agent 详情"""
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
return jsonify({'agent': agent.to_dict()}), 200
@agents_bp.route('/<agent_id>/status', methods=['GET'])
@jwt_required()
def get_agent_status(agent_id):
"""获取 Agent 实时状态"""
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
return jsonify({
'agent_id': agent.id,
'status': agent.status,
'current_sessions': agent.current_sessions,
'last_heartbeat': agent.last_heartbeat.isoformat() if agent.last_heartbeat else None
}), 200
@agents_bp.route('/<agent_id>/config', methods=['PUT'])
@jwt_required()
def update_agent_config(agent_id):
"""更新 Agent 配置"""
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
data = request.get_json()
if 'name' in data:
agent.name = data['name']
if 'display_name' in data:
agent.display_name = data['display_name']
if 'capabilities' in data:
agent.capabilities = data['capabilities']
if 'priority' in data:
agent.priority = data['priority']
if 'weight' in data:
agent.weight = data['weight']
db.session.commit()
return jsonify({'agent': agent.to_dict()}), 200
@agents_bp.route('/<agent_id>/heartbeat', methods=['POST'])
def agent_heartbeat(agent_id):
"""Agent 心跳上报"""
agent = Agent.query.get(agent_id)
if not agent:
return jsonify({'error': 'Agent not found'}), 404
agent.last_heartbeat = datetime.utcnow()
agent.status = 'online'
db.session.commit()
return jsonify({'status': 'ok'}), 200
@agents_bp.route('/available', methods=['GET'])
@jwt_required()
def get_available_agents():
"""获取可用 Agent 列表"""
agents = Agent.query.filter_by(status='online').all()
return jsonify({'agents': [a.to_dict() for a in agents]}), 200

142
app/routes/auth.py Normal file
View File

@@ -0,0 +1,142 @@
"""
认证路由
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import (
create_access_token, create_refresh_token,
jwt_required, get_jwt_identity
)
from datetime import datetime
import bcrypt
from app.models import db, User
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/register', methods=['POST'])
def register():
"""注册新用户"""
data = request.get_json()
username = data.get('username')
email = data.get('email')
password = data.get('password')
nickname = data.get('nickname')
if not all([username, email, password]):
return jsonify({'error': 'Missing required fields'}), 400
# 检查用户是否已存在
if User.query.filter_by(username=username).first():
return jsonify({'error': 'Username already exists'}), 400
if User.query.filter_by(email=email).first():
return jsonify({'error': 'Email already exists'}), 400
# 密码哈希
password_hash = bcrypt.hashpw(
password.encode('utf-8'),
bcrypt.gensalt()
).decode('utf-8')
# 创建用户
user = User(
username=username,
email=email,
password_hash=password_hash,
nickname=nickname or username
)
db.session.add(user)
db.session.commit()
return jsonify({
'message': 'User registered successfully',
'user': user.to_dict()
}), 201
@auth_bp.route('/login', methods=['POST'])
def login():
"""用户登录"""
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not all([username, password]):
return jsonify({'error': 'Missing username or password'}), 400
# 查找用户
user = User.query.filter_by(username=username).first()
if not user:
return jsonify({'error': 'Invalid username or password'}), 401
# 验证密码
if not bcrypt.checkpw(password.encode('utf-8'), user.password_hash.encode('utf-8')):
return jsonify({'error': 'Invalid username or password'}), 401
# 检查用户状态
if user.status != 'active':
return jsonify({'error': 'Account is disabled'}), 403
# 更新最后登录时间
user.last_login_at = datetime.utcnow()
db.session.commit()
# 生成 Token
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
return jsonify({
'access_token': access_token,
'refresh_token': refresh_token,
'user': user.to_dict()
}), 200
@auth_bp.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
"""刷新 Token"""
identity = get_jwt_identity()
access_token = create_access_token(identity=identity)
return jsonify({
'access_token': access_token
}), 200
@auth_bp.route('/me', methods=['GET'])
@jwt_required()
def get_current_user():
"""获取当前用户信息"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
return jsonify({'user': user.to_dict()}), 200
@auth_bp.route('/logout', methods=['POST'])
@jwt_required()
def logout():
"""用户登出"""
return jsonify({'message': 'Logged out successfully'}), 200
@auth_bp.route('/verify', methods=['POST'])
@jwt_required()
def verify_token():
"""验证 Token 有效性"""
user_id = get_jwt_identity()
user = User.query.get(user_id)
if not user:
return jsonify({'valid': False}), 401
return jsonify({
'valid': True,
'user': user.to_dict()
}), 200

99
app/routes/gateways.py Normal file
View File

@@ -0,0 +1,99 @@
"""
Gateway 路由
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required
from datetime import datetime
import bcrypt
from app.models import db, Gateway
gateways_bp = Blueprint('gateways', __name__)
@gateway_bp.route('/', methods=['GET'])
@jwt_required()
def get_gateways():
"""获取 Gateway 列表"""
gateways = Gateway.query.all()
return jsonify({'gateways': [g.to_dict() for g in gateways]}), 200
@gateway_bp.route('/', methods=['POST'])
@jwt_required()
def register_gateway():
"""注册 Gateway"""
data = request.get_json()
name = data.get('name')
url = data.get('url')
token = data.get('token')
if not all([name, url]):
return jsonify({'error': 'Missing required fields'}), 400
if Gateway.query.filter_by(name=name).first():
return jsonify({'error': 'Gateway name already exists'}), 400
# Token 哈希
token_hash = None
if token:
token_hash = bcrypt.hashpw(
token.encode('utf-8'),
bcrypt.gensalt()
).decode('utf-8')
gateway = Gateway(
name=name,
url=url,
token_hash=token_hash,
status='offline'
)
db.session.add(gateway)
db.session.commit()
return jsonify({'gateway': gateway.to_dict()}), 201
@gateway_bp.route('/<gateway_id>', methods=['DELETE'])
@jwt_required()
def delete_gateway(gateway_id):
"""注销 Gateway"""
gateway = Gateway.query.get(gateway_id)
if not gateway:
return jsonify({'error': 'Gateway not found'}), 404
db.session.delete(gateway)
db.session.commit()
return jsonify({'message': 'Gateway deleted'}), 200
@gateway_bp.route('/<gateway_id>/status', methods=['GET'])
@jwt_required()
def get_gateway_status(gateway_id):
"""获取 Gateway 状态"""
gateway = Gateway.query.get(gateway_id)
if not gateway:
return jsonify({'error': 'Gateway not found'}), 404
return jsonify({
'gateway_id': gateway.id,
'status': gateway.status,
'agent_count': gateway.agent_count,
'last_heartbeat': gateway.last_heartbeat.isoformat() if gateway.last_heartbeat else None
}), 200
@gateway_bp.route('/<gateway_id>/heartbeat', methods=['POST'])
def gateway_heartbeat(gateway_id):
"""Gateway 心跳上报"""
gateway = Gateway.query.get(gateway_id)
if not gateway:
return jsonify({'error': 'Gateway not found'}), 404
gateway.last_heartbeat = datetime.utcnow()
gateway.status = 'online'
db.session.commit()
return jsonify({'status': 'ok'}), 200

91
app/routes/messages.py Normal file
View File

@@ -0,0 +1,91 @@
"""
消息路由
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import datetime
from app.models import db, Message, Session
messages_bp = Blueprint('messages', __name__)
@messages_bp.route('/', methods=['POST'])
@jwt_required()
def send_message():
"""发送消息 (HTTP 方式)"""
user_id = get_jwt_identity()
data = request.get_json()
session_id = data.get('session_id')
content = data.get('content')
message_type = data.get('type', 'text')
reply_to = data.get('reply_to')
if not all([session_id, content]):
return jsonify({'error': 'Missing required fields'}), 400
# 验证会话
session = Session.query.filter_by(id=session_id, user_id=user_id).first()
if not session:
return jsonify({'error': 'Session not found'}), 404
# 创建消息
message = Message(
session_id=session_id,
sender_type='user',
sender_id=user_id,
message_type=message_type,
content=content,
reply_to=reply_to,
status='sent',
ack_status='pending'
)
db.session.add(message)
# 更新会话
session.message_count += 1
session.last_active_at = datetime.utcnow()
db.session.commit()
return jsonify({'message': message.to_dict()}), 201
@messages_bp.route('/<message_id>', methods=['GET'])
@jwt_required()
def get_message(message_id):
"""获取单条消息"""
message = Message.query.get(message_id)
if not message:
return jsonify({'error': 'Message not found'}), 404
return jsonify({'message': message.to_dict()}), 200
@messages_bp.route('/<message_id>/ack', methods=['PUT'])
def acknowledge_message(message_id):
"""确认消息已送达"""
message = Message.query.get(message_id)
if not message:
return jsonify({'error': 'Message not found'}), 404
message.ack_status = 'acknowledged'
message.delivered_at = datetime.utcnow()
db.session.commit()
return jsonify({'message': message.to_dict()}), 200
@messages_bp.route('/<message_id>/read', methods=['PUT'])
@jwt_required()
def mark_message_read(message_id):
"""标记消息已读"""
message = Message.query.get(message_id)
if not message:
return jsonify({'error': 'Message not found'}), 404
message.status = 'read'
db.session.commit()
return jsonify({'message': message.to_dict()}), 200

102
app/routes/sessions.py Normal file
View File

@@ -0,0 +1,102 @@
"""
会话路由
"""
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import datetime
from app.models import db, Session, Agent
sessions_bp = Blueprint('sessions', __name__)
@sessions_bp.route('/', methods=['GET'])
@jwt_required()
def get_sessions():
"""获取会话列表"""
user_id = get_jwt_identity()
sessions = Session.query.filter_by(user_id=user_id).all()
return jsonify({
'sessions': [s.to_dict() for s in sessions]
}), 200
@sessions_bp.route('/', methods=['POST'])
@jwt_required()
def create_session():
"""创建会话"""
user_id = get_jwt_identity()
data = request.get_json()
title = data.get('title', 'New Session')
agent_id = data.get('agent_id')
priority = data.get('priority', 5)
# 如果没有指定 Agent分配一个
if not agent_id:
agent = Agent.query.filter_by(status='online').order_by(
Agent.current_sessions.asc()
).first()
if agent:
agent_id = agent.id
session = Session(
user_id=user_id,
primary_agent_id=agent_id,
title=title,
status='active'
)
db.session.add(session)
db.session.commit()
return jsonify({
'session': session.to_dict()
}), 201
@sessions_bp.route('/<session_id>', methods=['GET'])
@jwt_required()
def get_session(session_id):
"""获取会话详情"""
user_id = get_jwt_identity()
session = Session.query.filter_by(id=session_id, user_id=user_id).first()
if not session:
return jsonify({'error': 'Session not found'}), 404
return jsonify({'session': session.to_dict()}), 200
@sessions_bp.route('/<session_id>/close', methods=['PUT'])
@jwt_required()
def close_session(session_id):
"""关闭会话"""
user_id = get_jwt_identity()
session = Session.query.filter_by(id=session_id, user_id=user_id).first()
if not session:
return jsonify({'error': 'Session not found'}), 404
session.status = 'closed'
session.updated_at = datetime.utcnow()
db.session.commit()
return jsonify({'message': 'Session closed', 'session': session.to_dict()}), 200
@sessions_bp.route('/<session_id>/messages', methods=['GET'])
@jwt_required()
def get_session_messages(session_id):
"""获取会话消息"""
user_id = get_jwt_identity()
session = Session.query.filter_by(id=session_id, user_id=user_id).first()
if not session:
return jsonify({'error': 'Session not found'}), 404
messages = session.messages.order_by('created_at').all()
return jsonify({
'messages': [m.to_dict() for m in messages]
}), 200

67
app/routes/stats.py Normal file
View File

@@ -0,0 +1,67 @@
"""
统计路由
"""
from flask import Blueprint, jsonify
from flask_jwt_extended import jwt_required
from app.models import db, User, Session, Agent, Gateway, Message
from sqlalchemy import func
stats_bp = Blueprint('stats', __name__)
@stats_bp.route('/', methods=['GET'])
@jwt_required()
def get_stats():
"""获取系统统计信息"""
stats = {
'users': User.query.count(),
'sessions': Session.query.filter_by(status='active').count(),
'agents': Agent.query.filter_by(status='online').count(),
'gateways': Gateway.query.filter_by(status='online').count(),
'messages': Message.query.count(),
}
return jsonify({'stats': stats}), 200
@stats_bp.route('/sessions', methods=['GET'])
@jwt_required()
def get_session_stats():
"""获取会话统计"""
stats = {
'total': Session.query.count(),
'active': Session.query.filter_by(status='active').count(),
'closed': Session.query.filter_by(status='closed').count(),
'paused': Session.query.filter_by(status='paused').count(),
}
return jsonify({'stats': stats}), 200
@stats_bp.route('/messages', methods=['GET'])
@jwt_required()
def get_message_stats():
"""获取消息统计"""
stats = {
'total': Message.query.count(),
'sent': Message.query.filter_by(status='sent').count(),
'delivered': Message.query.filter_by(status='delivered').count(),
'read': Message.query.filter_by(status='read').count(),
'failed': Message.query.filter_by(status='failed').count(),
}
return jsonify({'stats': stats}), 200
@stats_bp.route('/agents', methods=['GET'])
@jwt_required()
def get_agent_stats():
"""获取 Agent 统计"""
stats = {
'total': Agent.query.count(),
'online': Agent.query.filter_by(status='online').count(),
'offline': Agent.query.filter_by(status='offline').count(),
'busy': Agent.query.filter_by(status='busy').count(),
}
return jsonify({'stats': stats}), 200