Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 1m36s
263 lines
9.2 KiB
Python
263 lines
9.2 KiB
Python
"""
|
||
设备模块视图 - App端
|
||
"""
|
||
from rest_framework import viewsets, status
|
||
from rest_framework.decorators import action
|
||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||
from drf_spectacular.utils import extend_schema
|
||
|
||
from utils.response import success, error
|
||
from utils.exceptions import ErrorCode
|
||
from apps.admins.authentication import AppJWTAuthentication
|
||
from .models import Device, UserDevice, DeviceType, DeviceSettings, DeviceWifi
|
||
from .serializers import (
|
||
DeviceSerializer,
|
||
UserDeviceSerializer,
|
||
BindDeviceSerializer,
|
||
DeviceVerifySerializer,
|
||
DeviceTypeSerializer,
|
||
DeviceDetailSerializer,
|
||
DeviceSettingsUpdateSerializer,
|
||
)
|
||
|
||
|
||
@extend_schema(tags=['设备'])
|
||
class DeviceViewSet(viewsets.ViewSet):
|
||
"""设备视图集(App端)"""
|
||
|
||
authentication_classes = [AppJWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@action(detail=False, methods=['get'], url_path='query-by-mac',
|
||
authentication_classes=[], permission_classes=[AllowAny])
|
||
def query_by_mac(self, request):
|
||
"""
|
||
通过MAC地址查询SN码(无需登录)
|
||
GET /api/v1/devices/query-by-mac?mac=AA:BB:CC:DD:EE:FF
|
||
"""
|
||
mac = request.query_params.get('mac', '')
|
||
if not mac:
|
||
return error(message='MAC地址不能为空')
|
||
|
||
# 统一格式
|
||
mac = mac.upper().replace('-', ':')
|
||
|
||
try:
|
||
device = Device.objects.select_related('device_type').get(mac_address=mac)
|
||
return success(data={
|
||
'sn': device.sn,
|
||
'mac_address': device.mac_address,
|
||
'device_type': DeviceTypeSerializer(device.device_type).data if device.device_type else None,
|
||
'status': device.status,
|
||
'is_bound': device.status == 'bound'
|
||
})
|
||
except Device.DoesNotExist:
|
||
return error(
|
||
code=404,
|
||
message='未找到对应的设备,请检查MAC地址是否正确或设备是否已完成入库',
|
||
status_code=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def verify(self, request):
|
||
"""
|
||
验证设备SN
|
||
POST /api/v1/devices/verify
|
||
"""
|
||
serializer = DeviceVerifySerializer(data=request.data)
|
||
if not serializer.is_valid():
|
||
return error(message=str(serializer.errors))
|
||
|
||
sn = serializer.validated_data['sn']
|
||
|
||
try:
|
||
device = Device.objects.select_related('device_type').get(sn=sn)
|
||
except Device.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='设备不存在')
|
||
|
||
# 检查是否已被绑定
|
||
is_bindable = device.status != 'bound'
|
||
|
||
return success(data={
|
||
'sn': device.sn,
|
||
'is_bindable': is_bindable,
|
||
'device_type': DeviceTypeSerializer(device.device_type).data if device.device_type else None
|
||
})
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def bind(self, request):
|
||
"""
|
||
绑定设备
|
||
POST /api/v1/devices/bind
|
||
"""
|
||
serializer = BindDeviceSerializer(data=request.data)
|
||
if not serializer.is_valid():
|
||
return error(message=str(serializer.errors))
|
||
|
||
sn = serializer.validated_data['sn']
|
||
spirit_id = serializer.validated_data.get('spirit_id')
|
||
|
||
try:
|
||
device = Device.objects.get(sn=sn)
|
||
except Device.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='设备不存在')
|
||
|
||
# 检查是否已被绑定
|
||
if device.status == 'bound':
|
||
# 检查是否是当前用户绑定的
|
||
existing = UserDevice.objects.filter(device=device, is_active=True).first()
|
||
if existing and existing.user != request.user:
|
||
return error(code=ErrorCode.DEVICE_ALREADY_BOUND, message='设备已被其他用户绑定')
|
||
|
||
# 创建绑定关系
|
||
user_device, created = UserDevice.objects.update_or_create(
|
||
user=request.user,
|
||
device=device,
|
||
defaults={
|
||
'spirit_id': spirit_id,
|
||
'is_active': True
|
||
}
|
||
)
|
||
|
||
# 更新设备状态
|
||
device.status = 'bound'
|
||
device.save()
|
||
|
||
return success(
|
||
data=UserDeviceSerializer(user_device).data,
|
||
message='绑定成功' if created else '更新绑定成功'
|
||
)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def my_devices(self, request):
|
||
"""
|
||
我的设备列表
|
||
GET /api/v1/devices/my_devices
|
||
"""
|
||
user_devices = UserDevice.objects.filter(
|
||
user=request.user,
|
||
is_active=True
|
||
).select_related('device', 'device__device_type', 'spirit')
|
||
|
||
serializer = UserDeviceSerializer(user_devices, many=True)
|
||
return success(data=serializer.data)
|
||
|
||
@action(detail=True, methods=['delete'])
|
||
def unbind(self, request, pk=None):
|
||
"""
|
||
解绑设备
|
||
DELETE /api/v1/devices/{id}/unbind
|
||
"""
|
||
try:
|
||
user_device = UserDevice.objects.get(id=pk, user=request.user)
|
||
except UserDevice.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='绑定记录不存在')
|
||
|
||
# 更新绑定状态
|
||
user_device.is_active = False
|
||
user_device.save()
|
||
|
||
# 检查设备是否还有其他活跃绑定
|
||
active_bindings = UserDevice.objects.filter(device=user_device.device, is_active=True).count()
|
||
if active_bindings == 0:
|
||
user_device.device.status = 'out_stock'
|
||
user_device.device.save()
|
||
|
||
return success(message='解绑成功')
|
||
|
||
@action(detail=True, methods=['put'], url_path='update-spirit')
|
||
def update_spirit(self, request, pk=None):
|
||
"""
|
||
更新设备绑定的智能体
|
||
PUT /api/v1/devices/{id}/update-spirit
|
||
"""
|
||
try:
|
||
user_device = UserDevice.objects.get(id=pk, user=request.user, is_active=True)
|
||
except UserDevice.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='绑定记录不存在')
|
||
|
||
spirit_id = request.data.get('spirit_id')
|
||
user_device.spirit_id = spirit_id
|
||
user_device.save()
|
||
|
||
return success(data=UserDeviceSerializer(user_device).data, message='更新成功')
|
||
|
||
@action(detail=True, methods=['get'])
|
||
def detail(self, request, pk=None):
|
||
"""
|
||
获取设备详情
|
||
GET /api/v1/devices/{user_device_id}/detail/
|
||
pk 为 UserDevice 的 ID
|
||
"""
|
||
try:
|
||
user_device = UserDevice.objects.select_related(
|
||
'device', 'spirit'
|
||
).get(id=pk, user=request.user, is_active=True)
|
||
except UserDevice.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='绑定记录不存在')
|
||
|
||
device = user_device.device
|
||
serializer = DeviceDetailSerializer(
|
||
device, context={'user_device': user_device}
|
||
)
|
||
return success(data=serializer.data)
|
||
|
||
@action(detail=True, methods=['put'], url_path='settings')
|
||
def update_settings(self, request, pk=None):
|
||
"""
|
||
更新设备设置
|
||
PUT /api/v1/devices/{user_device_id}/settings/
|
||
"""
|
||
try:
|
||
user_device = UserDevice.objects.select_related('device').get(
|
||
id=pk, user=request.user, is_active=True
|
||
)
|
||
except UserDevice.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='绑定记录不存在')
|
||
|
||
serializer = DeviceSettingsUpdateSerializer(data=request.data)
|
||
if not serializer.is_valid():
|
||
return error(message=str(serializer.errors))
|
||
|
||
device = user_device.device
|
||
settings_obj, _ = DeviceSettings.objects.get_or_create(device=device)
|
||
|
||
for field, value in serializer.validated_data.items():
|
||
setattr(settings_obj, field, value)
|
||
settings_obj.save()
|
||
|
||
return success(message='设置已保存')
|
||
|
||
@action(detail=True, methods=['post'], url_path='wifi')
|
||
def configure_wifi(self, request, pk=None):
|
||
"""
|
||
配置设备WiFi
|
||
POST /api/v1/devices/{user_device_id}/wifi/
|
||
"""
|
||
try:
|
||
user_device = UserDevice.objects.select_related('device').get(
|
||
id=pk, user=request.user, is_active=True
|
||
)
|
||
except UserDevice.DoesNotExist:
|
||
return error(code=ErrorCode.DEVICE_NOT_FOUND, message='绑定记录不存在')
|
||
|
||
ssid = request.data.get('ssid')
|
||
if not ssid:
|
||
return error(message='WiFi名称不能为空')
|
||
|
||
device = user_device.device
|
||
|
||
# 将其他 WiFi 标记为未连接
|
||
DeviceWifi.objects.filter(device=device).update(is_connected=False)
|
||
|
||
# 创建或更新当前 WiFi 记录
|
||
wifi, _ = DeviceWifi.objects.update_or_create(
|
||
device=device,
|
||
ssid=ssid,
|
||
defaults={'is_connected': True}
|
||
)
|
||
|
||
# TODO: 通过设备通信协议下发 WiFi 配置(password 不存库)
|
||
|
||
return success(message='WiFi 配置成功')
|