低空智控平台 后端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.
 
 

390 lines
12 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 DockService struct{}
var DefaultDockService = &DockService{}
// withUserFilter admin 不过滤,普通用户按 user_id 过滤(GORM scope)
func withUserFilter(userID int64, isAdmin bool) func(*gorm.DB) *gorm.DB {
if isAdmin {
return func(db *gorm.DB) *gorm.DB { return db }
}
return func(db *gorm.DB) *gorm.DB { return db.Where("user_id = ?", userID) }
}
// withDockFilter admin 不过滤,普通用户按所属机巢过滤(用于无 user_id 的 dock 关联表,如 alarm / command_log)
func withDockFilter(userID int64, isAdmin bool) func(*gorm.DB) *gorm.DB {
if isAdmin {
return func(db *gorm.DB) *gorm.DB { return db }
}
return func(db *gorm.DB) *gorm.DB {
return db.Where("dock_id IN (SELECT dock_id FROM dock WHERE user_id = ?)", userID)
}
}
// GetPage 机巢分页列表
func (s *DockService) GetPage(userID int64, isAdmin bool, req *vo.DockPageReq) (*common.PageResponse[model.Dock], *common.BusiError) {
db := common.DB.Model(&model.Dock{}).Scopes(withUserFilter(userID, isAdmin))
if req.RegisterStatus != "" {
db = db.Where("register_status = ?", req.RegisterStatus)
}
if req.Keyword != "" {
kw := "%" + req.Keyword + "%"
db = db.Where("name LIKE ? OR dock_id LIKE ? OR code LIKE ?", kw, kw, kw)
}
var total int64
if err := db.Count(&total).Error; err != nil {
logger.ERROR("统计机巢失败", err)
return nil, common.ErrInternal
}
var list []model.Dock
if err := db.Scopes(req.Paginate).Order("created_at DESC, 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 *DockService) GetDetail(userID int64, isAdmin bool, id int64) (*vo.DockVO, *common.BusiError) {
var dock model.Dock
if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&dock, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrDockNotFound
}
return nil, common.ErrInternal
}
online, realtime := s.realtime(dock.DockID)
return &vo.DockVO{Dock: &dock, Online: online, Realtime: realtime}, nil
}
// GetStatus 机巢纯实时状态
func (s *DockService) GetStatus(userID int64, isAdmin bool, id int64) (*vo.DockStatusVO, *common.BusiError) {
var dock model.Dock
if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).Select("dock_id").First(&dock, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrDockNotFound
}
return nil, common.ErrInternal
}
online, realtime := s.realtime(dock.DockID)
return &vo.DockStatusVO{DockID: dock.DockID, Online: online, Realtime: realtime}, nil
}
// Create 手动登记机巢(归属当前用户;admin 可指定 userID)
func (s *DockService) Create(userID int64, isAdmin bool, req *vo.DockCreateReq) (*model.Dock, *common.BusiError) {
owner := userID
if isAdmin && req.UserID != 0 {
owner = req.UserID
}
var existing model.Dock
if err := common.DB.Where("dock_id = ?", req.DockID).First(&existing).Error; err == nil {
if existing.UserID != 0 {
return nil, common.ErrDockExists
}
updates := map[string]any{
"user_id": owner,
"register_status": "registered",
"updated_at": time.Now(),
}
if req.Name != "" {
updates["name"] = req.Name
}
if req.Code != "" {
updates["code"] = req.Code
}
if req.SN != "" {
updates["sn"] = req.SN
}
if req.Iccid != "" {
updates["iccid"] = req.Iccid
}
if req.Location != "" {
updates["location"] = req.Location
}
updates["longitude"] = req.Longitude
updates["latitude"] = req.Latitude
updates["altitude"] = req.Altitude
if err := common.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&existing).Updates(updates).Error; err != nil {
return err
}
return tx.Model(&model.Drone{}).Where("dock_id = ?", existing.DockID).Update("user_id", owner).Error
}); err != nil {
logger.ERROR("认领机巢失败", err)
return nil, common.ErrInternal
}
common.DB.First(&existing, existing.ID)
return &existing, nil
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
logger.ERROR("查询机巢失败", err)
return nil, common.ErrInternal
}
id, err := tool.NextID()
if err != nil {
return nil, common.ErrInternal
}
now := time.Now()
dock := &model.Dock{
ID: id,
UserID: owner,
DockID: req.DockID,
Name: req.Name,
Code: req.Code,
SN: req.SN,
Iccid: req.Iccid,
Location: req.Location,
Longitude: req.Longitude,
Latitude: req.Latitude,
Altitude: req.Altitude,
RegisterStatus: "registered",
DockIDSource: "manual",
ProtocolVer: "1.0",
CreatedAt: now,
UpdatedAt: now,
}
if err := common.DB.Create(dock).Error; err != nil {
if code, _ := common.ParseError(err); code == 1062 {
return nil, common.ErrDockExists
}
logger.ERROR("登记机巢失败", err)
return nil, common.ErrInternal
}
return dock, nil
}
// Update 更新/认领机巢
func (s *DockService) Update(userID int64, isAdmin bool, id int64, req *vo.DockUpdateReq) (*model.Dock, *common.BusiError) {
var dock model.Dock
if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&dock, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, common.ErrDockNotFound
}
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.Location != nil {
updates["location"] = *req.Location
}
if req.Longitude != nil {
updates["longitude"] = *req.Longitude
}
if req.Latitude != nil {
updates["latitude"] = *req.Latitude
}
if req.Altitude != nil {
updates["altitude"] = *req.Altitude
}
if isAdmin && req.UserID != 0 {
updates["user_id"] = req.UserID
}
if req.RegisterAs == "registered" {
updates["register_status"] = "registered"
if isAdmin && req.UserID != 0 {
updates["user_id"] = req.UserID
} else {
updates["user_id"] = userID // 普通用户认领 → 归属自己
}
}
if err := common.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&dock).Updates(updates).Error; err != nil {
return err
}
if owner, ok := updates["user_id"]; ok {
if err := tx.Model(&model.Drone{}).Where("dock_id = ?", dock.DockID).Update("user_id", owner).Error; err != nil {
return err
}
}
return nil
}); err != nil {
logger.ERROR("更新机巢失败", err)
return nil, common.ErrInternal
}
common.DB.First(&dock, id)
return &dock, nil
}
// Delete 硬删除机巢及其关联设备数据
func (s *DockService) Delete(userID int64, isAdmin bool, id int64) *common.BusiError {
var dock model.Dock
if err := common.DB.Scopes(withUserFilter(userID, isAdmin)).First(&dock, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.ErrDockNotFound
}
logger.ERROR("查询待删除机巢失败", err)
return common.ErrInternal
}
var taskIDs []string
var droneSNs []string
var simCardIDs []int64
if err := common.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.TaskPlan{}).Where("dock_id = ?", dock.DockID).Pluck("id", &taskIDs).Error; err != nil {
return err
}
if err := tx.Model(&model.Drone{}).Where("dock_id = ?", dock.DockID).Pluck("drone_sn", &droneSNs).Error; err != nil {
return err
}
if err := tx.Model(&model.SimCard{}).Where("dock_id = ?", dock.DockID).Pluck("id", &simCardIDs).Error; err != nil {
return err
}
if len(droneSNs) > 0 {
var videoIDs []int64
if err := tx.Model(&model.Video{}).Where("drone_sn IN ?", droneSNs).Pluck("id", &videoIDs).Error; err != nil {
return err
}
if len(videoIDs) > 0 {
if err := tx.Where("video_id IN ?", videoIDs).Delete(&model.DownloadLog{}).Error; err != nil {
return err
}
if err := tx.Where("id IN ?", videoIDs).Delete(&model.Video{}).Error; err != nil {
return err
}
}
}
if len(simCardIDs) > 0 {
if err := tx.Where("sim_card_id IN ?", simCardIDs).Delete(&model.SimUsageRecord{}).Error; err != nil {
return err
}
if err := tx.Where("sim_card_id IN ?", simCardIDs).Delete(&model.SimRechargeLog{}).Error; err != nil {
return err
}
if err := tx.Where("id IN ?", simCardIDs).Delete(&model.SimCard{}).Error; err != nil {
return err
}
}
for _, item := range []any{
&model.Alarm{}, &model.DeviceCommandLog{}, &model.WorkflowState{},
&model.TaskExecution{}, &model.TaskPlan{}, &model.LiveSession{}, &model.Drone{},
} {
if err := tx.Where("dock_id = ?", dock.DockID).Delete(item).Error; err != nil {
return err
}
}
return tx.Delete(&dock).Error
}); err != nil {
logger.ERROR("硬删除机巢失败", err)
return common.ErrInternal
}
for _, taskID := range taskIDs {
DefaultScheduler.RemoveTask(taskID)
}
_ = common.SetRemove(cache.OnlineDockSetKey, dock.DockID)
_ = common.Delete(cache.DockHeartbeatKeyOf(dock.DockID))
_ = common.Delete(cache.DockStatusKeyOf(dock.DockID))
return nil
}
// realtime 读取机巢实时状态(Redis Hash + 在线集合)
func (s *DockService) realtime(dockID string) (bool, map[string]string) {
realtime, err := common.HashGetAll(cache.DockStatusKeyOf(dockID))
if err != nil {
realtime = map[string]string{}
}
online, _ := common.SetMemberExists(cache.OnlineDockSetKey, dockID)
return online, realtime
}
// EnsureFromOnline 设备自发现:不存在则登记为 pending,存在则更新在线状态与软件版本
func (s *DockService) EnsureFromOnline(dockID string, st statusOnline) {
online := st.Status == "online"
var dock model.Dock
err := common.DB.Where("dock_id = ?", dockID).First(&dock).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
id, e := tool.NextID()
if e != nil {
logger.ERROR("生成机巢 ID 失败", e)
return
}
now := time.Now()
dock = model.Dock{
ID: id,
UserID: 0, // 待认领
DockID: dockID,
Name: st.Name,
Code: dockID,
SN: dockID,
Location: st.Location,
Latitude: st.Latitude,
Longitude: st.Longitude,
RegisterStatus: "pending",
DockIDSource: "auto",
SoftwareVer: st.SoftwareVersion,
ProtocolVer: st.ProtocolVersion,
CreatedAt: now,
UpdatedAt: now,
}
if err := common.DB.Create(&dock).Error; err != nil {
logger.ERROR("登记机巢失败", err)
return
}
} else if err == nil {
updates := map[string]any{"updated_at": time.Now()}
if st.SoftwareVersion != "" {
updates["software_ver"] = st.SoftwareVersion
}
if st.ProtocolVersion != "" {
updates["protocol_ver"] = st.ProtocolVersion
}
if dock.Name == "" && st.Name != "" {
updates["name"] = st.Name
}
if dock.Location == "" && st.Location != "" {
updates["location"] = st.Location
}
if dock.Latitude == 0 && st.Latitude != 0 {
updates["latitude"] = st.Latitude
}
if dock.Longitude == 0 && st.Longitude != 0 {
updates["longitude"] = st.Longitude
}
if err := common.DB.Model(&dock).Updates(updates).Error; err != nil {
logger.ERROR("更新机巢在线状态失败", err)
}
}
// 在线集合
if online {
_ = common.SetAdd(cache.OnlineDockSetKey, dockID)
refreshDeviceHeartbeat(cache.DockHeartbeatKeyOf(dockID))
} else {
_ = common.SetRemove(cache.OnlineDockSetKey, dockID)
_ = common.Delete(cache.DockHeartbeatKeyOf(dockID))
}
broadcast("dock.status", map[string]any{
"dockId": dockID,
"online": online,
"softwareVer": st.SoftwareVersion,
"protocolVer": st.ProtocolVersion,
})
}