2026-04-04 11:19:01 +08:00
|
|
|
|
from rest_framework import serializers, status, views
|
|
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
|
from django.contrib.auth import authenticate, get_user_model
|
|
|
|
|
|
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginSerializer(serializers.Serializer):
|
|
|
|
|
|
username = serializers.CharField()
|
|
|
|
|
|
password = serializers.CharField()
|
2026-04-04 16:41:13 +08:00
|
|
|
|
mode = serializers.ChoiceField(
|
|
|
|
|
|
choices=['solo', 'team', 'agent_only'],
|
|
|
|
|
|
default='solo',
|
|
|
|
|
|
help_text='solo=单枪匹马,team=组队团战,agent_only=独当一面'
|
|
|
|
|
|
)
|
|
|
|
|
|
agent_ids = serializers.ListField(
|
|
|
|
|
|
child=serializers.CharField(),
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=list,
|
|
|
|
|
|
help_text='选择的龙虾 ID 列表(team 或 agent_only 模式)'
|
2026-04-04 12:53:02 +08:00
|
|
|
|
)
|
2026-04-04 11:19:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginView(views.APIView):
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
|
serializer = LoginSerializer(data=request.data)
|
|
|
|
|
|
if not serializer.is_valid():
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
username = serializer.validated_data['username']
|
|
|
|
|
|
password = serializer.validated_data['password']
|
2026-04-04 16:41:13 +08:00
|
|
|
|
mode = serializer.validated_data.get('mode', 'solo')
|
|
|
|
|
|
agent_ids = serializer.validated_data.get('agent_ids', [])
|
2026-04-04 11:19:01 +08:00
|
|
|
|
|
|
|
|
|
|
user = authenticate(username=username, password=password)
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
return Response(
|
|
|
|
|
|
{'detail': '用户名或密码错误'},
|
|
|
|
|
|
status=status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 简单 Token(生产环境应该用 JWT)
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
token = uuid.uuid4().hex
|
|
|
|
|
|
|
2026-04-04 12:53:02 +08:00
|
|
|
|
# 构建会话信息
|
|
|
|
|
|
sessions = []
|
|
|
|
|
|
|
2026-04-04 16:41:13 +08:00
|
|
|
|
if mode in ['solo', 'team']:
|
|
|
|
|
|
# 人类身份(单枪匹马 或 组队团战)
|
2026-04-04 12:53:02 +08:00
|
|
|
|
sessions.append({
|
|
|
|
|
|
'session_type': 'human',
|
|
|
|
|
|
'nickname': user.username,
|
|
|
|
|
|
'emoji': '👤',
|
|
|
|
|
|
'user_id': user.id
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-04-04 16:41:13 +08:00
|
|
|
|
if mode in ['team', 'agent_only']:
|
|
|
|
|
|
# 龙虾身份(组队团战 或 独当一面)
|
|
|
|
|
|
if not agent_ids:
|
|
|
|
|
|
return Response(
|
|
|
|
|
|
{'error': '组队或独当一面模式需要选择至少一只龙虾'},
|
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加所有选择的龙虾
|
|
|
|
|
|
for agent_id in agent_ids:
|
|
|
|
|
|
agent = user.get_linked_agent(agent_id)
|
2026-04-04 12:53:02 +08:00
|
|
|
|
if agent:
|
|
|
|
|
|
sessions.append({
|
|
|
|
|
|
'session_type': 'agent',
|
|
|
|
|
|
'agent_id': agent['agent_id'],
|
|
|
|
|
|
'agent_name': agent['agent_name'],
|
|
|
|
|
|
'nickname': agent['agent_name'],
|
|
|
|
|
|
'emoji': agent.get('agent_emoji', '🤖'),
|
|
|
|
|
|
'instance_id': agent.get('instance_id')
|
|
|
|
|
|
})
|
|
|
|
|
|
else:
|
|
|
|
|
|
return Response(
|
2026-04-04 16:41:13 +08:00
|
|
|
|
{'error': f'未找到绑定的龙虾:{agent_id}'},
|
2026-04-04 12:53:02 +08:00
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
)
|
2026-04-04 11:19:01 +08:00
|
|
|
|
|
2026-04-04 16:41:13 +08:00
|
|
|
|
# 模式名称映射
|
|
|
|
|
|
mode_names = {
|
|
|
|
|
|
'solo': '单枪匹马',
|
|
|
|
|
|
'team': '组队团战',
|
|
|
|
|
|
'agent_only': '独当一面'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 11:19:01 +08:00
|
|
|
|
return Response({
|
|
|
|
|
|
'token': token,
|
|
|
|
|
|
'user': {
|
|
|
|
|
|
'id': user.id,
|
|
|
|
|
|
'username': user.username,
|
2026-04-04 12:53:02 +08:00
|
|
|
|
'email': user.email,
|
|
|
|
|
|
'linked_agents': user.linked_agents
|
|
|
|
|
|
},
|
|
|
|
|
|
'sessions': sessions,
|
2026-04-04 16:41:13 +08:00
|
|
|
|
'mode': mode,
|
|
|
|
|
|
'mode_name': mode_names.get(mode, mode)
|
2026-04-04 11:19:01 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegisterSerializer(serializers.Serializer):
|
|
|
|
|
|
username = serializers.CharField()
|
|
|
|
|
|
email = serializers.EmailField()
|
|
|
|
|
|
password = serializers.CharField()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegisterView(views.APIView):
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
|
serializer = RegisterSerializer(data=request.data)
|
|
|
|
|
|
if not serializer.is_valid():
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
user = User.objects.create_user(
|
|
|
|
|
|
username=serializer.validated_data['username'],
|
|
|
|
|
|
email=serializer.validated_data['email'],
|
|
|
|
|
|
password=serializer.validated_data['password']
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
token = uuid.uuid4().hex
|
|
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
|
'token': token,
|
|
|
|
|
|
'user': {
|
|
|
|
|
|
'id': user.id,
|
|
|
|
|
|
'username': user.username,
|
|
|
|
|
|
'email': user.email
|
|
|
|
|
|
}
|
|
|
|
|
|
}, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return Response(
|
|
|
|
|
|
{'detail': str(e)},
|
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
)
|
2026-04-04 12:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LinkedAgentsView(views.APIView):
|
|
|
|
|
|
"""
|
|
|
|
|
|
用户绑定的龙虾管理
|
|
|
|
|
|
|
|
|
|
|
|
GET /api/v1/user/linked-agents/?username=xxx - 获取龙虾列表
|
|
|
|
|
|
POST /api/v1/user/linked-agents/ - 添加龙虾
|
|
|
|
|
|
DELETE /api/v1/user/linked-agents/{agent_id}/?username=xxx - 移除龙虾
|
|
|
|
|
|
"""
|
|
|
|
|
|
def get(self, request):
|
|
|
|
|
|
username = request.query_params.get('username')
|
|
|
|
|
|
if not username:
|
|
|
|
|
|
return Response({'error': '缺少 username'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
|
|
return Response({
|
|
|
|
|
|
'linked_agents': user.linked_agents,
|
|
|
|
|
|
'count': len(user.linked_agents)
|
|
|
|
|
|
})
|
|
|
|
|
|
except User.DoesNotExist:
|
|
|
|
|
|
return Response({'error': '用户不存在'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
|
username = request.data.get('username')
|
|
|
|
|
|
agent_id = request.data.get('agent_id')
|
|
|
|
|
|
agent_name = request.data.get('agent_name')
|
|
|
|
|
|
agent_emoji = request.data.get('agent_emoji', '🤖')
|
|
|
|
|
|
instance_id = request.data.get('instance_id')
|
|
|
|
|
|
|
|
|
|
|
|
if not all([username, agent_id, agent_name]):
|
|
|
|
|
|
return Response({'error': '缺少必要参数'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
|
|
user.add_linked_agent(agent_id, agent_name, agent_emoji, instance_id)
|
|
|
|
|
|
return Response({
|
|
|
|
|
|
'status': 'success',
|
|
|
|
|
|
'linked_agents': user.linked_agents
|
|
|
|
|
|
})
|
|
|
|
|
|
except User.DoesNotExist:
|
|
|
|
|
|
return Response({'error': '用户不存在'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
def delete(self, request, agent_id):
|
|
|
|
|
|
username = request.query_params.get('username')
|
|
|
|
|
|
if not username:
|
|
|
|
|
|
return Response({'error': '缺少 username'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
|
|
user.remove_linked_agent(agent_id)
|
|
|
|
|
|
return Response({
|
|
|
|
|
|
'status': 'success',
|
|
|
|
|
|
'linked_agents': user.linked_agents
|
|
|
|
|
|
})
|
|
|
|
|
|
except User.DoesNotExist:
|
|
|
|
|
|
return Response({'error': '用户不存在'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScanLocalAgentsView(views.APIView):
|
|
|
|
|
|
"""
|
|
|
|
|
|
扫描本机龙虾列表
|
|
|
|
|
|
|
2026-04-04 17:05:16 +08:00
|
|
|
|
GET /api/v1/user/scan-local-agents/?instance_id=xxx&username=xxx - 扫描指定实例
|
|
|
|
|
|
GET /api/v1/user/scan-local-agents/?username=xxx - 扫描所有实例
|
2026-04-04 12:53:02 +08:00
|
|
|
|
"""
|
|
|
|
|
|
def get(self, request):
|
|
|
|
|
|
instance_id = request.query_params.get('instance_id')
|
2026-04-04 17:05:16 +08:00
|
|
|
|
username = request.query_params.get('username')
|
|
|
|
|
|
|
|
|
|
|
|
# 获取用户绑定的龙虾信息(用于获取 agent_name 和 emoji)
|
|
|
|
|
|
user_agents = {}
|
|
|
|
|
|
if username:
|
|
|
|
|
|
from django.contrib.auth import get_user_model
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
try:
|
|
|
|
|
|
user = User.objects.get(username=username)
|
|
|
|
|
|
for agent in user.linked_agents:
|
|
|
|
|
|
user_agents[agent['agent_id']] = agent
|
|
|
|
|
|
except User.DoesNotExist:
|
|
|
|
|
|
pass
|
2026-04-04 12:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
if not instance_id:
|
|
|
|
|
|
# 返回所有已注册实例的 Agent
|
|
|
|
|
|
from instances.models import Instance
|
|
|
|
|
|
instances = Instance.objects.filter(is_active=True)
|
|
|
|
|
|
agents = []
|
|
|
|
|
|
for inst in instances:
|
|
|
|
|
|
for agent_id in inst.agent_ids:
|
2026-04-04 17:05:16 +08:00
|
|
|
|
agent_info = user_agents.get(agent_id, {})
|
2026-04-04 12:53:02 +08:00
|
|
|
|
agents.append({
|
|
|
|
|
|
'agent_id': agent_id,
|
2026-04-04 17:05:16 +08:00
|
|
|
|
'agent_name': agent_info.get('agent_name', agent_id),
|
|
|
|
|
|
'agent_emoji': agent_info.get('agent_emoji', '🤖'),
|
2026-04-04 12:53:02 +08:00
|
|
|
|
'instance_id': inst.instance_id,
|
|
|
|
|
|
'instance_name': inst.instance_name
|
|
|
|
|
|
})
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 返回指定实例的 Agent
|
|
|
|
|
|
from instances.models import Instance
|
|
|
|
|
|
try:
|
|
|
|
|
|
instance = Instance.objects.get(instance_id=instance_id, is_active=True)
|
2026-04-04 17:05:16 +08:00
|
|
|
|
for agent_id in instance.agent_ids:
|
|
|
|
|
|
agent_info = user_agents.get(agent_id, {})
|
|
|
|
|
|
agents.append({
|
|
|
|
|
|
'agent_id': agent_id,
|
|
|
|
|
|
'agent_name': agent_info.get('agent_name', agent_id),
|
|
|
|
|
|
'agent_emoji': agent_info.get('agent_emoji', '🤖'),
|
|
|
|
|
|
'instance_id': instance.instance_id,
|
|
|
|
|
|
'instance_name': instance.instance_name
|
|
|
|
|
|
})
|
2026-04-04 12:53:02 +08:00
|
|
|
|
except Instance.DoesNotExist:
|
|
|
|
|
|
return Response({'error': '实例不存在'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
|
'agents': agents,
|
|
|
|
|
|
'count': len(agents)
|
|
|
|
|
|
})
|