低空智控平台 后端go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

220 lines
5.8 KiB

package service
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
"gorm.io/gorm"
"laic-backend/cache"
"laic-backend/common"
"laic-backend/logger"
"laic-backend/model"
"laic-backend/mqtt"
"laic-backend/tool"
"laic-backend/vo"
)
type FirmwareService struct{}
var DefaultFirmwareService = &FirmwareService{}
const defaultFirmwareComponent = "dock-edge-agent"
// GetPage 固件分页列表(admin)
func (s *FirmwareService) GetPage(req *vo.FirmwarePageReq) (*common.PageResponse[model.Firmware], *common.BusiError) {
db := common.DB.Model(&model.Firmware{})
if req.Status != "" {
db = db.Where("status = ?", req.Status)
}
if req.Component != "" {
db = db.Where("component = ?", req.Component)
}
if req.Keyword != "" {
kw := "%" + req.Keyword + "%"
db = db.Where("version LIKE ? OR description LIKE ?", kw, kw)
}
var total int64
if err := db.Count(&total).Error; err != nil {
logger.ERROR("统计固件失败", err)
return nil, common.ErrInternal
}
var list []model.Firmware
if err := db.Scopes(req.Paginate).Order("id DESC").Find(&list).Error; err != nil {
logger.ERROR("查询固件列表失败", err)
return nil, common.ErrInternal
}
return common.Page(req.Pagination, total, list), nil
}
// GetReleasedList 已发布固件列表(供普通用户选择升级)
func (s *FirmwareService) GetReleasedList() ([]model.Firmware, *common.BusiError) {
var list []model.Firmware
if err := common.DB.Where("status = ?", "released").Order("id DESC").Find(&list).Error; err != nil {
logger.ERROR("查询已发布固件失败", err)
return nil, common.ErrInternal
}
return list, nil
}
// GetDetail 固件详情(admin)
func (s *FirmwareService) GetDetail(id int64) (*model.Firmware, *common.BusiError) {
var fw model.Firmware
if err := common.DB.First(&fw, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrFirmwareNotFound
}
return nil, common.ErrInternal
}
return &fw, nil
}
// Create 新增固件版本(admin)
func (s *FirmwareService) Create(adminID int64, req *vo.FirmwareCreateReq) (*model.Firmware, *common.BusiError) {
id, err := tool.NextID()
if err != nil {
return nil, common.ErrInternal
}
component := req.Component
if component == "" {
component = defaultFirmwareComponent
}
now := time.Now()
fw := &model.Firmware{
ID: id,
Component: component,
Version: req.Version,
Description: req.Description,
FileURL: req.FileURL,
Sha256: req.Sha256,
Signature: req.Signature,
FileSize: req.FileSize,
Mandatory: req.Mandatory,
Status: "draft",
CreatedBy: adminID,
CreatedAt: now,
UpdatedAt: now,
}
if err := common.DB.Create(fw).Error; err != nil {
if code, _ := common.ParseError(err); code == 1062 {
return nil, common.ErrFirmwareVersionExist
}
logger.ERROR("新增固件失败", err)
return nil, common.ErrInternal
}
return fw, nil
}
// Update 编辑固件(admin)
func (s *FirmwareService) Update(id int64, req *vo.FirmwareUpdateReq) *common.BusiError {
var fw model.Firmware
if err := common.DB.First(&fw, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.ErrFirmwareNotFound
}
return common.ErrInternal
}
updates := map[string]any{"updated_at": time.Now()}
if req.Description != "" {
updates["description"] = req.Description
}
if req.FileURL != "" {
updates["file_url"] = req.FileURL
}
if req.Sha256 != "" {
updates["sha256"] = req.Sha256
}
if req.Signature != "" {
updates["signature"] = req.Signature
}
if req.FileSize != 0 {
updates["file_size"] = req.FileSize
}
if req.Mandatory != nil {
updates["mandatory"] = *req.Mandatory
}
if req.Status != "" {
updates["status"] = req.Status
}
if err := common.DB.Model(&fw).Updates(updates).Error; err != nil {
logger.ERROR("编辑固件失败", err)
return common.ErrInternal
}
return nil
}
// Delete 删除固件(admin)
func (s *FirmwareService) Delete(id int64) *common.BusiError {
res := common.DB.Delete(&model.Firmware{}, id)
if res.Error != nil {
logger.ERROR("删除固件失败", res.Error)
return common.ErrInternal
}
if res.RowsAffected == 0 {
return common.ErrFirmwareNotFound
}
return nil
}
// Upgrade 下发 OTA 升级:校验机巢归属+在线 → 校验固件已发布 → MQTT ota/desired
func (s *FirmwareService) Upgrade(userID int64, isAdmin bool, dockID int64, firmwareID int64) (*vo.FirmwareUpgradeVO, *common.BusiError) {
var dock model.Dock
if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&dock, dockID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrDockNotFound
}
return nil, common.ErrInternal
}
online, _ := common.SetMemberExists(cache.OnlineDockSetKey, dock.DockID)
if !online {
return nil, common.ErrDockOffline
}
var fw model.Firmware
if err := common.DB.First(&fw, firmwareID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrFirmwareNotFound
}
return nil, common.ErrInternal
}
if fw.Status != "released" {
return nil, common.ErrFirmwareNotReleased
}
updateID, err := tool.NextID()
if err != nil {
return nil, common.ErrInternal
}
payload := map[string]any{
"updateId": strconv.FormatInt(updateID, 10),
"component": fw.Component,
"version": fw.Version,
"url": fw.FileURL,
"sha256": fw.Sha256,
"signature": fw.Signature,
"mandatory": fw.Mandatory == 1,
"issuedAt": time.Now().UnixMilli(),
}
b, err := json.Marshal(payload)
if err != nil {
return nil, common.ErrInternal
}
topic := fmt.Sprintf("dock-edge/v1/dock/%s/ota/desired", dock.DockID)
if err := mqtt.Publish(topic, 1, true, b); err != nil {
logger.ERROR("下发 OTA 升级失败", err)
return nil, common.ErrInternal
}
return &vo.FirmwareUpgradeVO{
UpdateID: strconv.FormatInt(updateID, 10),
DockID: dock.DockID,
Firmware: &fw,
}, nil
}