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.
197 lines
5.7 KiB
197 lines
5.7 KiB
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.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) {
|
|
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,
|
|
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,
|
|
"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,
|
|
})
|
|
}
|
|
|