🔔 飞行侠实现:实例注册 + Webhook 推送

新功能:
- instances 应用:OpenClaw 实例管理
- Instance 模型:实例注册,Agent 列表,Webhook URL
- MeetingInstanceMap:会议 - 实例映射
- Webhook 推送:消息发送时自动通知相关实例

API 端点:
- POST /api/v1/instances/register/ - 实例注册
- POST /api/v1/instances/join-meeting/ - 加入会议
- GET  /api/v1/instances/ - 实例列表
- POST /api/v1/instances/webhook-test/ - Webhook 测试

集成:
- send_message API 自动触发 Webhook 推送
- 支持广播和定向推送

测试:
- test_webhook.py: 完整测试流程

使用场景:
1. 每台 OpenClaw 机器注册实例
2. Agent 加入会议时关联实例
3. 消息发送时推送到对应机器
4. 本机 OpenClaw 收到通知,触发 Agent 响应
This commit is contained in:
2026-04-04 12:19:43 +08:00
parent 09f2bb9b6c
commit 929459fd33
12 changed files with 574 additions and 0 deletions

118
backend/instances/views.py Normal file
View File

@@ -0,0 +1,118 @@
from rest_framework import serializers, status, views
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Instance, MeetingInstanceMap
from .webhook import register_instance, join_meeting
import logging
logger = logging.getLogger(__name__)
class InstanceRegisterSerializer(serializers.Serializer):
instance_id = serializers.CharField(max_length=100)
instance_name = serializers.CharField(max_length=200)
agent_ids = serializers.ListField(child=serializers.CharField())
webhook_url = serializers.URLField()
class InstanceRegisterView(views.APIView):
"""
实例注册接口
POST /api/v1/instances/register/
{
"instance_id": "phospher-openclaw",
"instance_name": "飞行侠的 OpenClaw",
"agent_ids": ["flying_hero", "lobster_monitor"],
"webhook_url": "http://192.168.1.100:8888/meeting-notify"
}
"""
def post(self, request):
serializer = InstanceRegisterSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try:
instance = register_instance(
instance_id=serializer.validated_data['instance_id'],
instance_name=serializer.validated_data['instance_name'],
agent_ids=serializer.validated_data['agent_ids'],
webhook_url=serializer.validated_data['webhook_url']
)
return Response({
'status': 'success',
'instance_id': str(instance.id),
'message': f'实例 {instance.instance_name} 注册成功'
})
except Exception as e:
logger.error(f"注册失败:{e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class MeetingJoinSerializer(serializers.Serializer):
instance_id = serializers.CharField(max_length=100)
meeting_id = serializers.UUIDField()
agent_ids = serializers.ListField(child=serializers.CharField())
class MeetingJoinView(views.APIView):
"""
实例加入会议
POST /api/v1/instances/join-meeting/
{
"instance_id": "phospher-openclaw",
"meeting_id": "xxx-xxx-xxx",
"agent_ids": ["flying_hero"]
}
"""
def post(self, request):
serializer = MeetingJoinSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try:
join_meeting(
instance_id=serializer.validated_data['instance_id'],
meeting_id=serializer.validated_data['meeting_id'],
agent_ids=serializer.validated_data['agent_ids']
)
return Response({'status': 'success', 'message': '已加入会议'})
except Instance.DoesNotExist:
return Response({'error': '实例不存在'}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"加入会议失败:{e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class InstanceListView(views.APIView):
"""
获取实例列表
GET /api/v1/instances/
"""
def get(self, request):
instances = Instance.objects.filter(is_active=True)
data = [{
'id': str(i.id),
'instance_id': i.instance_id,
'instance_name': i.instance_name,
'agent_ids': i.agent_ids,
'webhook_url': i.webhook_url,
'last_heartbeat': i.last_heartbeat.isoformat() if i.last_heartbeat else None
} for i in instances]
return Response(data)
class WebhookNotifyView(views.APIView):
"""
Webhook 通知接收(测试用)
POST /api/v1/instances/webhook-test/
"""
def post(self, request):
logger.info(f"📬 收到 Webhook 通知:{request.data}")
return Response({'status': 'received'})