feat: Phase 1 - 核心功能实现
This commit is contained in:
3
app/routes/__init__.py
Normal file
3
app/routes/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
路由模块
|
||||
"""
|
||||
91
app/routes/agents.py
Normal file
91
app/routes/agents.py
Normal 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
142
app/routes/auth.py
Normal 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
99
app/routes/gateways.py
Normal 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
91
app/routes/messages.py
Normal 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
102
app/routes/sessions.py
Normal 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
67
app/routes/stats.py
Normal 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
|
||||
Reference in New Issue
Block a user