Add OTA package upload functionality and UI enhancements

- Introduced a new endpoint for uploading OTA packages in backend/routes/ota.py.
- Implemented file handling and MD5 checksum calculation for OTA packages.
- Updated frontend API to support OTA package uploads with a new uploadOtaPackage function.
- Enhanced the OTA management UI to include file upload options and improved layout.
- Updated requirements.txt to include boto3 for S3 interactions.
- Made various UI adjustments for better user experience and consistency.
This commit is contained in:
yangy
2026-05-15 17:34:54 +08:00
parent 661b85ce62
commit 1a6c96da8c
9 changed files with 511 additions and 132 deletions
+72 -1
View File
@@ -1,11 +1,22 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Body
from fastapi import APIRouter, Depends, HTTPException, Query, Body, UploadFile, File, Form
from sqlalchemy.orm import Session
from typing import List, Optional
import logging
from dotenv import load_dotenv
from database import get_db
from models.ota import Ota
from schemas import OtaCreate, OtaUpdate, OtaResponse
from response import ApiResponse, PageData
from ota_storage import (
OTA_MODEL_X8,
OTA_MODEL_X9,
OTA_UPLOAD_MODELS,
read_upload_content_and_md5,
save_x9_package_local,
upload_x8_package_to_s3,
)
load_dotenv()
# Configure logging
logger = logging.getLogger(__name__)
@@ -13,6 +24,66 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/ota", tags=["ota"])
@router.post("/upload-package", response_model=ApiResponse)
async def upload_ota_package(
model: str = Form(..., description="设备型号"),
package_file: UploadFile = File(..., description="OTA 升级包"),
):
"""上传 OTA 升级包:计算 MD5X8 上传 S3 为 LUXSIN_X8.PKGX9 本地保存为 LUXSIN.PKG"""
try:
model = (model or "").strip()
if model not in OTA_UPLOAD_MODELS:
return ApiResponse(
code=0,
msg=f"当前仅支持为 {OTA_MODEL_X8}{OTA_MODEL_X9} 上传升级包",
data=None,
)
if not package_file or not package_file.filename:
return ApiResponse(code=0, msg="请选择升级包文件", data=None)
logger.info(
"Uploading OTA package: model=%s, filename=%s",
model,
package_file.filename,
)
content, md5_hex = await read_upload_content_and_md5(package_file)
if model == OTA_MODEL_X9:
saved_name, download_url = save_x9_package_local(content, md5_hex)
return ApiResponse(
code=1,
msg="success",
data={
"md5": md5_hex,
"filename": saved_name,
"url": download_url,
},
)
if model == OTA_MODEL_X8:
saved_name, download_url, s3_key = upload_x8_package_to_s3(
content, md5_hex
)
return ApiResponse(
code=1,
msg="success",
data={
"md5": md5_hex,
"filename": saved_name,
"url": download_url,
"s3_key": s3_key,
},
)
return ApiResponse(code=0, msg="不支持的设备型号", data=None)
except ValueError as e:
return ApiResponse(code=0, msg=str(e), data=None)
except Exception as e:
logger.error("Error uploading OTA package: %s", e, exc_info=True)
return ApiResponse(code=0, msg=f"上传失败:{e}", data=None)
@router.get("/", response_model=ApiResponse)
def get_ota_list(
skip: int = Query(0, ge=0, description="跳过记录数"),