lty/qy_lty/device_interaction/serializers.py
pmc 33b302c773 fix(affinity-P1): CR-001 + IN-005 修复 UserDevice 软删语义 + is_bound 改名
UserDevice.is_active 改名为 is_bound(消除与 Device.is_active 的命名冲突),
新增 ActiveUserDeviceManager(active manager),4 处控制权解析调用点
(MAC 登录、bind_status、绑定校验、RTC token、绑定 endpoint)切换到
UserDevice.active.filter(...),避免 P2 软删后旧绑定者被签发 user-token、
WS 分组路由错误、RTC 房间归属错乱等安全 / 越权风险。

base_manager_name='objects' 保证 admin 默认 queryset 不受 active 过滤影响。

详见 docs/REVIEW-affinity-P1.md CR-001 / IN-005。
2026-05-13 10:10:14 +08:00

130 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
from rest_framework import serializers
from django.core.cache import cache
from django.utils import timezone
from .models import DeviceType, DeviceBatch, Device, UserDevice
class DeviceTypeSerializer(serializers.ModelSerializer):
class Meta:
model = DeviceType
fields = '__all__'
class DeviceBatchSerializer(serializers.ModelSerializer):
device_type_name = serializers.ReadOnlyField(source='device_type.name')
class Meta:
model = DeviceBatch
fields = '__all__'
class DeviceSerializer(serializers.ModelSerializer):
device_type_name = serializers.ReadOnlyField(source='device_type.name')
batch_number = serializers.ReadOnlyField(source='batch.batch_number')
class Meta:
model = Device
fields = '__all__'
read_only_fields = ('device_code',)
class DeviceCreateSerializer(serializers.ModelSerializer):
firmware_version = serializers.CharField(max_length=50, required=False)
wifi_name = serializers.CharField(max_length=100, required=False)
wifi_password = serializers.CharField(max_length=100, required=False)
brightness = serializers.IntegerField(min_value=0, max_value=100, required=False)
class Meta:
model = Device
fields = [
'device_type', 'batch', 'serial_number', 'mac_address',
'firmware_version', 'wifi_name', 'wifi_password', 'brightness'
]
class DeviceBatchCreateSerializer(serializers.Serializer):
device_type = serializers.PrimaryKeyRelatedField(queryset=DeviceType.objects.all())
batch = serializers.PrimaryKeyRelatedField(queryset=DeviceBatch.objects.all())
start_serial = serializers.CharField(max_length=50)
count = serializers.IntegerField(min_value=1, max_value=1000)
mac_prefix = serializers.CharField(max_length=9, required=False) # 可选MAC地址前缀
def validate(self, data):
# 检查批次数量是否足够
batch = data['batch']
if batch.device_type.id != data['device_type'].id:
raise serializers.ValidationError("所选批次不属于所选设备类型")
# 检查设备数量是否足够
existing_devices = Device.objects.filter(batch=batch).count()
if existing_devices + data['count'] > batch.quantity:
raise serializers.ValidationError(f"批次总量为{batch.quantity},已创建{existing_devices}台,无法再创建{data['count']}")
return data
class UserDeviceSerializer(serializers.ModelSerializer):
device_code = serializers.ReadOnlyField(source='device.device_code')
mac_address = serializers.ReadOnlyField(source='device.mac_address')
device_type = serializers.ReadOnlyField(source='device.device_type.name')
device_status = serializers.ReadOnlyField(source='device.status')
is_online = serializers.SerializerMethodField()
battery_level = serializers.ReadOnlyField(source='device.battery_level')
firmware_version = serializers.ReadOnlyField(source='device.firmware_version')
wifi_name = serializers.ReadOnlyField(source='device.wifi_name')
wifi_password = serializers.ReadOnlyField(source='device.wifi_password')
brightness = serializers.ReadOnlyField(source='device.brightness')
def get_is_online(self, obj):
"""根据Redis中的last_seen判断设备是否在线3分钟内有上报"""
last_seen = cache.get(f"device:last_seen:{obj.device.mac_address}")
if last_seen is None:
return False
return (time.time() - float(last_seen)) <= 180
class Meta:
model = UserDevice
fields = '__all__'
read_only_fields = ('user', 'bound_at')
class DeviceRegisterSerializer(serializers.Serializer):
mac_address = serializers.CharField(max_length=17)
device_type_code = serializers.CharField(max_length=10, required=False, default='T01')
firmware_version = serializers.CharField(max_length=50, required=False, default='')
def validate_mac_address(self, value):
if Device.objects.filter(mac_address=value).exists():
raise serializers.ValidationError("设备已注册")
return value
def validate_device_type_code(self, value):
if not DeviceType.objects.filter(code=value).exists():
raise serializers.ValidationError(f"设备类型 {value} 不存在")
return value
class DeviceBindSerializer(serializers.Serializer):
mac_address = serializers.CharField(max_length=17)
nickname = serializers.CharField(max_length=100, required=False)
is_primary = serializers.BooleanField(default=False, required=False)
def validate_mac_address(self, value):
try:
device = Device.objects.get(mac_address=value)
# 检查设备是否已被激活
if not device.is_active:
device.is_active = True
device.activated_at = timezone.now()
device.save()
except Device.DoesNotExist:
raise serializers.ValidationError("设备不存在")
# 检查设备是否已被其他用户「有效」绑定(测试 MAC 跳过此检查)
# 使用 active manager 显式过滤 is_bound=True忽略软删历史绑定P1 收尾 CR-001
if value != 'AA:BB:CC:DD:EE:FF' and UserDevice.active.filter(device=device).exists():
raise serializers.ValidationError("设备已被其他用户绑定")
return value