462 lines
18 KiB
Python
462 lines
18 KiB
Python
|
|
"""智能终端(云端孪生镜像)— API 视图"""
|
|||
|
|
import uuid
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
|
|||
|
|
from django.conf import settings
|
|||
|
|
from django.db.models import Q
|
|||
|
|
from django.http import FileResponse, JsonResponse, StreamingHttpResponse
|
|||
|
|
from django.shortcuts import get_object_or_404
|
|||
|
|
from django.utils.timezone import now
|
|||
|
|
from minio import Minio
|
|||
|
|
from rest_framework import status
|
|||
|
|
from rest_framework.authentication import BaseAuthentication
|
|||
|
|
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
|||
|
|
from rest_framework.exceptions import AuthenticationFailed
|
|||
|
|
from rest_framework.pagination import PageNumberPagination
|
|||
|
|
from rest_framework.permissions import BasePermission, IsAuthenticated
|
|||
|
|
from rest_framework.response import Response
|
|||
|
|
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
|
|||
|
|
from rest_framework_simplejwt.tokens import AccessToken
|
|||
|
|
|
|||
|
|
from apps.common.serializers import AiSmartTerminalSerializer
|
|||
|
|
from apps.core.models import AiSmartTerminal, AiAlgorithmModels
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─── 设备认证 ──────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
class EdgeDeviceAuthentication(BaseAuthentication):
|
|||
|
|
"""边缘设备 JWT Token 认证类
|
|||
|
|
|
|||
|
|
验证 Token 中的 stcd 与请求中的 stcd 一致,确保设备只能操作自己的数据。
|
|||
|
|
"""
|
|||
|
|
keyword = "Bearer"
|
|||
|
|
|
|||
|
|
def authenticate(self, request):
|
|||
|
|
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
|
|||
|
|
if not auth_header.startswith(self.keyword):
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
raw_token = auth_header[len(self.keyword):].strip()
|
|||
|
|
try:
|
|||
|
|
token = AccessToken(raw_token)
|
|||
|
|
except (InvalidToken, TokenError) as e:
|
|||
|
|
raise AuthenticationFailed(f"无效的认证令牌: {str(e)}")
|
|||
|
|
|
|||
|
|
token_type = token.get("type") or token.get("sub")
|
|||
|
|
if token_type != "device":
|
|||
|
|
raise AuthenticationFailed("此令牌不是设备认证令牌")
|
|||
|
|
|
|||
|
|
token_stcd = token.get("stcd")
|
|||
|
|
if not token_stcd:
|
|||
|
|
raise AuthenticationFailed("令牌中缺少设备编码(stcd)")
|
|||
|
|
|
|||
|
|
# 验证请求中的 stcd 与 Token 中的一致
|
|||
|
|
request_stcd = (
|
|||
|
|
request.GET.get("stcd")
|
|||
|
|
or (request.data.get("stcd") if hasattr(request, "data") else None)
|
|||
|
|
)
|
|||
|
|
if request_stcd and token_stcd != request_stcd:
|
|||
|
|
raise AuthenticationFailed("令牌中的设备编码与请求不匹配")
|
|||
|
|
|
|||
|
|
terminal = AiSmartTerminal.objects.filter(stcd=token_stcd).first()
|
|||
|
|
if terminal is None:
|
|||
|
|
raise AuthenticationFailed(f"设备未注册: {token_stcd}")
|
|||
|
|
|
|||
|
|
return (terminal, token)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class EdgeDevicePermission(BasePermission):
|
|||
|
|
"""边缘设备请求权限 — 仅允许通过 EdgeDeviceAuthentication 认证的请求"""
|
|||
|
|
def has_permission(self, request, view):
|
|||
|
|
return bool(request.user and isinstance(request.user, AiSmartTerminal))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _minio_client():
|
|||
|
|
return Minio(
|
|||
|
|
settings.MINIO_ENDPOINT,
|
|||
|
|
access_key=settings.MINIO_ACCESS_KEY,
|
|||
|
|
secret_key=settings.MINIO_SECRET_KEY,
|
|||
|
|
secure=settings.MINIO_SECURE,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─── 设备认证 — Token 生成 ────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@api_view(["POST"])
|
|||
|
|
def terminal_device_auth(request):
|
|||
|
|
"""设备接入认证 — 生成边缘设备 JWT Token(有效期24小时)"""
|
|||
|
|
stcd = str(request.data.get("stcd", "")).strip()
|
|||
|
|
if not stcd:
|
|||
|
|
return Response({"error": "缺少必要参数:stcd"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
terminal = AiSmartTerminal.objects.filter(stcd=stcd).first()
|
|||
|
|
if terminal is None:
|
|||
|
|
return Response({"error": f"未找到终端:{stcd}"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
token = AccessToken()
|
|||
|
|
token["stcd"] = stcd
|
|||
|
|
token["type"] = "device"
|
|||
|
|
token.set_exp(lifetime=timedelta(days=1))
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"access_token": str(token),
|
|||
|
|
"token_type": "bearer",
|
|||
|
|
"expires_in": 86400,
|
|||
|
|
"stcd": stcd,
|
|||
|
|
"stnm": terminal.stnm or "",
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["POST"])
|
|||
|
|
@permission_classes([IsAuthenticated])
|
|||
|
|
def create_smart_terminal(request):
|
|||
|
|
"""创建智能终端设备"""
|
|||
|
|
data = request.data.copy()
|
|||
|
|
stcd = data.get("stcd")
|
|||
|
|
if stcd and AiSmartTerminal.objects.filter(stcd=stcd).exists():
|
|||
|
|
return Response({"error": f"终端编码 '{stcd}' 已存在"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
terminal_id = uuid.uuid4().hex
|
|||
|
|
data["id"] = terminal_id
|
|||
|
|
data["record_user"] = request.user.username
|
|||
|
|
serializer = AiSmartTerminalSerializer(data=data)
|
|||
|
|
if serializer.is_valid():
|
|||
|
|
serializer.save()
|
|||
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|||
|
|
return Response({"error": "无效数据", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["PUT"])
|
|||
|
|
@permission_classes([IsAuthenticated])
|
|||
|
|
def update_smart_terminal(request):
|
|||
|
|
"""修改智能设备信息"""
|
|||
|
|
terminal_id = request.data.get("id")
|
|||
|
|
terminal = get_object_or_404(AiSmartTerminal, id=terminal_id)
|
|||
|
|
# 不允许通过此接口修改模型发布字段和边缘同步字段
|
|||
|
|
_protected_fields = {
|
|||
|
|
"model_id", "model_version", "model_path",
|
|||
|
|
"model_publish_time", "model_publish_user",
|
|||
|
|
"edge_model_version", "edge_update_time",
|
|||
|
|
"last_heartbeat_time", "online_status",
|
|||
|
|
}
|
|||
|
|
update_data = {k: v for k, v in request.data.items() if k not in _protected_fields}
|
|||
|
|
serializer = AiSmartTerminalSerializer(instance=terminal, data=update_data, partial=True)
|
|||
|
|
if serializer.is_valid():
|
|||
|
|
serializer.save()
|
|||
|
|
return Response(serializer.data)
|
|||
|
|
return Response({"error": "无效数据", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["PUT"])
|
|||
|
|
@permission_classes([IsAuthenticated])
|
|||
|
|
def publish_terminal_model(request):
|
|||
|
|
"""发布更新设备算法模型 — 将训练好的模型发布到一个或多个云端孪生设备
|
|||
|
|
支持传 id(单个)或 ids(列表),如 {"ids":["id1","id2"],"model_id":"...","model_version":"v1","model_path":"..."}
|
|||
|
|
"""
|
|||
|
|
# 归一化:支持 id(单个)或 ids(列表)
|
|||
|
|
raw_ids = request.data.get("ids", [])
|
|||
|
|
single_id = request.data.get("id")
|
|||
|
|
if single_id:
|
|||
|
|
raw_ids = [single_id]
|
|||
|
|
if isinstance(raw_ids, str):
|
|||
|
|
import json as _json
|
|||
|
|
try:
|
|||
|
|
raw_ids = _json.loads(raw_ids)
|
|||
|
|
except Exception:
|
|||
|
|
raw_ids = [raw_ids]
|
|||
|
|
ids = [tid for tid in raw_ids if tid] if isinstance(raw_ids, list) else []
|
|||
|
|
model_id = request.data.get("model_id")
|
|||
|
|
# 查找算法模型
|
|||
|
|
ai_model = get_object_or_404(AiAlgorithmModels, id=model_id)
|
|||
|
|
model_version = ai_model.model_version or ""
|
|||
|
|
model_path = ai_model.onnx_bucketpath or ai_model.model_bucketpath
|
|||
|
|
if not model_path:
|
|||
|
|
return Response({"error": "模型文件未存储在MinIO中"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
model_name = model_path.split("/")[-1]
|
|||
|
|
|
|||
|
|
if not ids:
|
|||
|
|
return Response({"error": "缺少必要参数:id 或 ids"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
if not all([model_id, model_version, model_path]):
|
|||
|
|
return Response({"error": "缺少必要参数:model_id, model_version, model_path"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
terminals = AiSmartTerminal.objects.filter(id__in=ids)
|
|||
|
|
if not terminals.exists():
|
|||
|
|
return Response({"error": "未找到匹配的终端设备"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
publish_time = now()
|
|||
|
|
publish_user = request.user.username
|
|||
|
|
terminals.update(
|
|||
|
|
model_id=model_id,
|
|||
|
|
model_version=model_version,
|
|||
|
|
model_path=model_path,
|
|||
|
|
model_name=model_name,
|
|||
|
|
model_publish_time=publish_time,
|
|||
|
|
model_publish_user=publish_user,
|
|||
|
|
)
|
|||
|
|
updated = AiSmartTerminal.objects.filter(id__in=ids)
|
|||
|
|
serializer = AiSmartTerminalSerializer(updated, many=True)
|
|||
|
|
return Response({
|
|||
|
|
"published_count": len(ids),
|
|||
|
|
"terminals": serializer.data,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["GET"])
|
|||
|
|
def search_smart_terminals(request):
|
|||
|
|
"""条件查询智能设备列表(无需登录,边缘端也要调用)"""
|
|||
|
|
filter_kwargs = {}
|
|||
|
|
stcd = request.GET.get("stcd", "")
|
|||
|
|
stnm = request.GET.get("stnm", "")
|
|||
|
|
sttp = request.GET.get("sttp", "")
|
|||
|
|
model_name = request.GET.get("model_name", "")
|
|||
|
|
online_status = request.GET.get("online_status", "")
|
|||
|
|
usfl = request.GET.get("usfl", "")
|
|||
|
|
is_deleted = request.GET.get("is_deleted", "0")
|
|||
|
|
page_size = request.GET.get("page_size") or 10
|
|||
|
|
if stcd:
|
|||
|
|
filter_kwargs["stcd__icontains"] = stcd
|
|||
|
|
if stnm:
|
|||
|
|
filter_kwargs["stnm__icontains"] = stnm
|
|||
|
|
if sttp:
|
|||
|
|
filter_kwargs["sttp"] = sttp
|
|||
|
|
if model_name:
|
|||
|
|
filter_kwargs["model_name__icontains"] = model_name
|
|||
|
|
if online_status:
|
|||
|
|
filter_kwargs["online_status"] = int(online_status)
|
|||
|
|
if usfl:
|
|||
|
|
filter_kwargs["usfl"] = int(usfl)
|
|||
|
|
filter_kwargs["is_deleted"] = is_deleted
|
|||
|
|
queryset = AiSmartTerminal.objects.filter(**filter_kwargs).order_by("order_index", "stcd")
|
|||
|
|
paginator = PageNumberPagination()
|
|||
|
|
paginator.page_size = page_size
|
|||
|
|
page = paginator.paginate_queryset(queryset, request)
|
|||
|
|
serializer = AiSmartTerminalSerializer(page, many=True)
|
|||
|
|
return paginator.get_paginated_response(serializer.data)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["PUT"])
|
|||
|
|
@authentication_classes([EdgeDeviceAuthentication])
|
|||
|
|
@permission_classes([EdgeDevicePermission])
|
|||
|
|
def update_terminal_heartbeat(request):
|
|||
|
|
"""更新设备在线状态(边缘上报心跳)— 使用设备 Token 认证"""
|
|||
|
|
terminal = request.user # EdgeDeviceAuthentication 已将 terminal 注入为 request.user
|
|||
|
|
if not isinstance(terminal, AiSmartTerminal):
|
|||
|
|
return Response({"error": "设备认证失败"}, status=status.HTTP_401_UNAUTHORIZED)
|
|||
|
|
|
|||
|
|
update_fields = ["last_heartbeat_time", "online_status"]
|
|||
|
|
was_offline = terminal.online_status == 0
|
|||
|
|
terminal.last_heartbeat_time = now()
|
|||
|
|
terminal.online_status = 1
|
|||
|
|
|
|||
|
|
# 首次接入:标记数据已接入
|
|||
|
|
if not terminal.dtin:
|
|||
|
|
terminal.dtin = 1
|
|||
|
|
terminal.dtin_tm = now()
|
|||
|
|
update_fields.extend(["dtin", "dtin_tm"])
|
|||
|
|
|
|||
|
|
terminal.save(update_fields=update_fields)
|
|||
|
|
|
|||
|
|
# 终端从离线恢复上线时,通过 WebSocket 推送通知
|
|||
|
|
if was_offline:
|
|||
|
|
_push_terminal_online_notification(terminal)
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"stcd": terminal.stcd,
|
|||
|
|
"online_status": terminal.online_status,
|
|||
|
|
"last_heartbeat_time": terminal.last_heartbeat_time,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _push_terminal_online_notification(terminal):
|
|||
|
|
"""通过 WebSocket 向前端推送终端上线通知"""
|
|||
|
|
try:
|
|||
|
|
from asgiref.sync import async_to_sync
|
|||
|
|
from channels.layers import get_channel_layer
|
|||
|
|
|
|||
|
|
channel_layer = get_channel_layer()
|
|||
|
|
if channel_layer is not None:
|
|||
|
|
async_to_sync(channel_layer.group_send)(
|
|||
|
|
"terminal_status",
|
|||
|
|
{
|
|||
|
|
"type": "terminal.status",
|
|||
|
|
"data": {
|
|||
|
|
"event": "online",
|
|||
|
|
"count": 1,
|
|||
|
|
"terminals": [
|
|||
|
|
{"stcd": terminal.stcd, "stnm": terminal.stnm}
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─── 模型版本检查 ──────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@api_view(["GET"])
|
|||
|
|
@authentication_classes([EdgeDeviceAuthentication])
|
|||
|
|
@permission_classes([EdgeDevicePermission])
|
|||
|
|
def check_terminal_model_version(request):
|
|||
|
|
"""检查云端模型版本 — 边缘端定时轮询是否有新版本模型"""
|
|||
|
|
terminal = request.user
|
|||
|
|
if not isinstance(terminal, AiSmartTerminal):
|
|||
|
|
return Response({"error": "设备认证失败"}, status=status.HTTP_401_UNAUTHORIZED)
|
|||
|
|
|
|||
|
|
if not terminal.model_id:
|
|||
|
|
return Response({
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"having_newmodel": 0,
|
|||
|
|
"model_version": "",
|
|||
|
|
"sync_check_interval": terminal.sync_check_interval or 30,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
ai_model = AiAlgorithmModels.objects.filter(id=terminal.model_id).first()
|
|||
|
|
if ai_model is None:
|
|||
|
|
return Response({
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"having_newmodel": 0,
|
|||
|
|
"model_version": terminal.model_version or "",
|
|||
|
|
"sync_check_interval": terminal.sync_check_interval or 30,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
edge_version = request.GET.get("edge_version", "")
|
|||
|
|
cloud_version = ai_model.model_version or ""
|
|||
|
|
|
|||
|
|
having_newmodel = 0
|
|||
|
|
if not edge_version or edge_version != cloud_version:
|
|||
|
|
having_newmodel = 1
|
|||
|
|
|
|||
|
|
result = {
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"having_newmodel": having_newmodel,
|
|||
|
|
"sync_check_interval": terminal.sync_check_interval or 30,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if having_newmodel:
|
|||
|
|
result["data"].update({
|
|||
|
|
"model_name": terminal.model_name or "",
|
|||
|
|
"model_version": cloud_version,
|
|||
|
|
"model_path": terminal.model_path or "",
|
|||
|
|
"model_publish_time": terminal.model_publish_time.isoformat() if terminal.model_publish_time else "",
|
|||
|
|
})
|
|||
|
|
else:
|
|||
|
|
result["data"]["model_version"] = cloud_version
|
|||
|
|
|
|||
|
|
return Response(result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["PUT"])
|
|||
|
|
@authentication_classes([EdgeDeviceAuthentication])
|
|||
|
|
@permission_classes([EdgeDevicePermission])
|
|||
|
|
def update_terminal_model_version(request):
|
|||
|
|
"""更新边缘端模型版本 — 边缘端下载新模型后回调,更新 edge_model_version 和 edge_update_time"""
|
|||
|
|
terminal = request.user
|
|||
|
|
if not isinstance(terminal, AiSmartTerminal):
|
|||
|
|
return Response({"error": "设备认证失败"}, status=status.HTTP_401_UNAUTHORIZED)
|
|||
|
|
|
|||
|
|
edge_model_version = str(request.data.get("edge_model_version", "")).strip()
|
|||
|
|
if not edge_model_version:
|
|||
|
|
return Response({"error": "缺少必要参数:edge_model_version"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
terminal.edge_model_version = edge_model_version
|
|||
|
|
terminal.edge_update_time = now()
|
|||
|
|
terminal.save(update_fields=["edge_model_version", "edge_update_time"])
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"stcd": terminal.stcd,
|
|||
|
|
"edge_model_version": terminal.edge_model_version,
|
|||
|
|
"edge_update_time": terminal.edge_update_time,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─── 模型下载(流式) ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _stream_object(response, chunk_size: int = 1024 * 1024):
|
|||
|
|
try:
|
|||
|
|
while True:
|
|||
|
|
chunk = response.read(chunk_size)
|
|||
|
|
if not chunk:
|
|||
|
|
break
|
|||
|
|
yield chunk
|
|||
|
|
finally:
|
|||
|
|
response.close()
|
|||
|
|
response.release_conn()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def download_terminal_model(request):
|
|||
|
|
"""下载最新版本设备算法模型 — 边缘端在校验版本差异后,从此接口下载模型文件"""
|
|||
|
|
stcd = str(request.GET.get("stcd") or "").strip()
|
|||
|
|
model_format = request.GET.get("model_format") or request.GET.get("format", "onnx")
|
|||
|
|
if not stcd:
|
|||
|
|
return JsonResponse({"error": "缺少必要参数:stcd"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
terminal = AiSmartTerminal.objects.filter(stcd=stcd).first()
|
|||
|
|
if terminal is None:
|
|||
|
|
return JsonResponse({"error": f"未找到终端:{stcd}"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
if not terminal.model_id:
|
|||
|
|
return JsonResponse({"error": "该终端尚未发布模型"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from apps.common.minio_client import minio_storage
|
|||
|
|
|
|||
|
|
ai_model = AiAlgorithmModels.objects.filter(id=terminal.model_id).first()
|
|||
|
|
if ai_model is None:
|
|||
|
|
return JsonResponse(
|
|||
|
|
{"error": f"终端已绑定的模型不存在:{terminal.model_id}"},
|
|||
|
|
status=status.HTTP_404_NOT_FOUND,
|
|||
|
|
)
|
|||
|
|
if model_format == "onnx":
|
|||
|
|
model_path = ai_model.onnx_bucketpath
|
|||
|
|
if not model_path:
|
|||
|
|
return JsonResponse({"error": "该模型尚未转换为ONNX格式"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
else:
|
|||
|
|
model_path = ai_model.model_bucketpath
|
|||
|
|
if not model_path:
|
|||
|
|
return JsonResponse({"error": "该模型没有原始模型文件"}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
parts = model_path.split('/')
|
|||
|
|
if len(parts) < 2:
|
|||
|
|
return JsonResponse({"error": f"模型存储路径无效:{model_path}"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
bucket_name = parts[0]
|
|||
|
|
object_name = '/'.join(parts[1:])
|
|||
|
|
if not minio_storage.object_exists(bucket_name, object_name):
|
|||
|
|
return JsonResponse(
|
|||
|
|
{"error": f"MinIO中未找到模型文件:{bucket_name}/{object_name}"},
|
|||
|
|
status=status.HTTP_404_NOT_FOUND,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
response = minio_storage.get_object(bucket_name, object_name)
|
|||
|
|
content_type = "application/octet-stream"
|
|||
|
|
filename = object_name.rsplit("/", 1)[-1]
|
|||
|
|
stream = StreamingHttpResponse(_stream_object(response), content_type=content_type)
|
|||
|
|
stream["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|||
|
|
return stream
|
|||
|
|
except Exception as e:
|
|||
|
|
return JsonResponse({"error": f"模型文件下载失败:{str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(["DELETE"])
|
|||
|
|
@permission_classes([IsAuthenticated])
|
|||
|
|
def delete_smart_terminal(request):
|
|||
|
|
"""删除智能设备"""
|
|||
|
|
terminal_id = request.GET.get("id")
|
|||
|
|
if not terminal_id:
|
|||
|
|
return Response({"error": "缺少必要参数:id"}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
terminal = get_object_or_404(AiSmartTerminal, id=terminal_id)
|
|||
|
|
terminal.delete()
|
|||
|
|
return Response({"message": "删除成功", "id": terminal_id})
|