package service import ( "errors" "time" "gorm.io/gorm" "laic-backend/cache" "laic-backend/common" "laic-backend/logger" "laic-backend/model" "laic-backend/tool" "laic-backend/vo" ) type DroneService struct{} var DefaultDroneService = &DroneService{} // GetPage 无人机分页列表 func (s *DroneService) GetPage(userID int64, isAdmin bool, req *vo.DronePageReq) (*common.PageResponse[model.Drone], *common.BusiError) { db := common.DB.Model(&model.Drone{}).Scopes(withUserFilter(userID, isAdmin)) if req.Status != "" { db = db.Where("status = ?", req.Status) } if req.Keyword != "" { kw := "%" + req.Keyword + "%" db = db.Where("drone_sn LIKE ? OR name LIKE ? OR code LIKE ? OR model LIKE ?", kw, kw, kw, kw) } var total int64 if err := db.Count(&total).Error; err != nil { logger.ERROR("统计无人机失败", err) return nil, common.ErrInternal } var list []model.Drone 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 } // GetDetail 无人机详情(含实时遥测) func (s *DroneService) GetDetail(userID int64, isAdmin bool, id int64) (*vo.DroneVO, *common.BusiError) { var drone model.Drone if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&drone, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, common.ErrDroneNotFound } return nil, common.ErrInternal } online, realtime := s.realtime(drone.DockID, drone.DroneSN) return &vo.DroneVO{Drone: &drone, Online: online, Realtime: realtime}, nil } // GetTelemetry 无人机实时遥测(当前值,历史曲线走 TDengine,见后续阶段) func (s *DroneService) GetTelemetry(userID int64, isAdmin bool, id int64) (map[string]any, *common.BusiError) { var drone model.Drone if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&drone, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, common.ErrDroneNotFound } return nil, common.ErrInternal } online, realtime := s.realtime(drone.DockID, drone.DroneSN) return map[string]any{ "droneSn": drone.DroneSN, "online": online, "realtime": realtime, }, nil } // Update 更新无人机基础信息 func (s *DroneService) Update(userID int64, isAdmin bool, id int64, req *vo.DroneUpdateReq) (*model.Drone, *common.BusiError) { var drone model.Drone if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&drone, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, common.ErrDroneNotFound } return nil, common.ErrInternal } updates := map[string]any{"updated_at": time.Now()} if req.Name != "" { updates["name"] = req.Name } if req.Code != "" { updates["code"] = req.Code } if req.Model != "" { updates["model"] = req.Model } if err := common.DB.Model(&drone).Updates(updates).Error; err != nil { logger.ERROR("更新无人机失败", err) return nil, common.ErrInternal } common.DB.First(&drone, id) return &drone, nil } // Delete 删除无人机 func (s *DroneService) Delete(userID int64, isAdmin bool, id int64) *common.BusiError { res := common.DB.Scopes(withUserFilter(userID, isAdmin)).Delete(&model.Drone{}, id) if res.Error != nil { logger.ERROR("删除无人机失败", res.Error) return common.ErrInternal } if res.RowsAffected == 0 { return common.ErrDroneNotFound } return nil } // realtime 读取无人机实时遥测(Redis Hash,按 dock_id 存储)+ 在线集合 func (s *DroneService) realtime(dockID, droneSN string) (bool, map[string]string) { realtime, err := common.HashGetAll(cache.DroneTelemetryKeyOf(dockID)) if err != nil { realtime = map[string]string{} } online, _ := common.SetMemberExists(cache.OnlineDroneSetKey, droneSN) return online, realtime } // EnsureFromState 无人机自动关联:按 droneSn 或 dockId 创建/更新(1:1 绑定) func (s *DroneService) EnsureFromState(dockID, droneSN, name string, online bool, battery int, firmwareVer string) { status := "offline" if online { status = "online" } if droneSN == "" { return } var dock model.Dock if err := common.DB.Select("user_id").Where("dock_id = ?", dockID).First(&dock).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { logger.ERROR("查询无人机关联机巢失败", err) } return } var drone model.Drone err := common.DB.Where("drone_sn = ? OR dock_id = ?", droneSN, dockID).First(&drone).Error if errors.Is(err, gorm.ErrRecordNotFound) { id, e := tool.NextID() if e != nil { logger.ERROR("生成无人机 ID 失败", e) return } now := time.Now() drone = model.Drone{ ID: id, UserID: dock.UserID, DroneSN: droneSN, DockID: dockID, Name: name, Status: status, Battery: battery, FirmwareVer: firmwareVer, CreatedAt: now, UpdatedAt: now, } if err := common.DB.Create(&drone).Error; err != nil { logger.ERROR("创建无人机失败", err) return } } else if err == nil { updates := map[string]any{ "user_id": dock.UserID, "drone_sn": droneSN, "dock_id": dockID, "status": status, "battery": battery, "updated_at": time.Now(), } if firmwareVer != "" { updates["firmware_ver"] = firmwareVer } if name != "" { updates["name"] = name } if err := common.DB.Model(&drone).Updates(updates).Error; err != nil { logger.ERROR("更新无人机失败", err) } } // 在线集合 if online { _ = common.SetAdd(cache.OnlineDroneSetKey, droneSN) refreshDeviceHeartbeat(cache.DroneHeartbeatKeyOf(droneSN)) } else { _ = common.SetRemove(cache.OnlineDroneSetKey, droneSN) _ = common.Delete(cache.DroneHeartbeatKeyOf(droneSN)) } broadcast("drone.status", map[string]any{ "dockId": dockID, "droneSn": droneSN, "online": online, "battery": battery, "firmwareVer": firmwareVer, }) }