package uhf import ( "errors" "fmt" "strings" "wails-app/internal/dao" "wails-app/internal/global" "wails-app/internal/model/uhf/request" "wails-app/internal/model/uhf/response" "go.uber.org/zap" ) type uhfService struct{} var UHFService = new(uhfService) // validateReaderRequest 校验设备连接参数,并返回通道所属停车场。 func validateReaderRequest(connectType, deviceCode string, channelID int, ip string, port int, comPort string, baudRate int) (uint, error) { if global.GVA_DB == nil { return 0, errors.New("数据库未初始化") } if strings.TrimSpace(deviceCode) == "" { return 0, errors.New("设备编码不能为空") } switch connectType { case dao.ConnectTypeTCP: if strings.TrimSpace(ip) == "" || port < 1 || port > 65535 { return 0, errors.New("TCP设备必须配置有效的IP和端口") } case dao.ConnectTypeSerial: if strings.TrimSpace(comPort) == "" || baudRate <= 0 { return 0, errors.New("串口设备必须配置串口和波特率") } case dao.ConnectTypeMQTT: if channelID <= 0 { return 0, errors.New("MQTT设备必须绑定通道") } default: return 0, fmt.Errorf("不支持的连接类型: %s", connectType) } if channelID <= 0 { return 0, nil } var channel dao.Channel if result := global.GVA_DB.Preload("Booth").Where("id = ?", channelID).First(&channel); result.Error != nil { return 0, fmt.Errorf("通道不存在: %d", channelID) } if channel.BoothID == 0 || channel.ParkingLotID == 0 || channel.Booth == nil { return 0, errors.New("通道未完整配置岗亭和停车场") } if channel.Booth.ParkingLotID != channel.ParkingLotID { return 0, errors.New("通道、岗亭和停车场归属不一致") } return channel.ParkingLotID, nil } // CreateReader 创建UHF读写器 func (s *uhfService) CreateReader(req request.UHFReaderCreate) error { parkingLotID, err := validateReaderRequest(req.ConnectType, req.DeviceCode, req.ChannelId, req.IPAddress, req.Port, req.COMPort, req.BaudRate) if err != nil { return err } var reader dao.UHFReader result := global.GVA_DB.Where("device_code = ?", req.DeviceCode).First(&reader) if result.RowsAffected > 0 { return fmt.Errorf("设备编码已存在") } var channel dao.Channel global.GVA_DB.Model(&channel).Where("id = ?", req.ChannelId).First(&channel) var booth dao.Booth global.GVA_DB.Model(&booth).Where("id = ?", channel.BoothID).First(&booth) // MQTT 设备必须绑定通道(路由依赖 channel -> booth -> lot 坐标) if req.ConnectType == dao.ConnectTypeMQTT && req.ChannelId == 0 { return fmt.Errorf("MQTT 设备必须绑定通道") } reader = dao.UHFReader{ DeviceCode: req.DeviceCode, DeviceName: req.DeviceName, DeviceType: req.DeviceType, ConnectType: req.ConnectType, IPAddress: req.IPAddress, Port: req.Port, COMPort: req.COMPort, BaudRate: req.BaudRate, IsActive: req.IsActive, Description: req.Description, ParkingLotID: parkingLotID, ChannelID: uint(req.ChannelId), } err = global.GVA_DB.Create(&reader).Error if req.IsActive { if err := StartDeviceHandler(&reader); err != nil { global.GVA_LOG.Error("启动设备失败", zap.String("code", reader.DeviceCode), zap.Error(err)) } } return err } // UpdateReader 更新UHF读写器 func (s *uhfService) UpdateReader(req request.UHFReaderUpdate) error { if global.GVA_DB == nil { return errors.New("数据库未初始化") } var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", req.ID).First(&reader) deviceCode := req.DeviceCode if strings.TrimSpace(deviceCode) == "" { deviceCode = reader.DeviceCode } parkingLotID, validationErr := validateReaderRequest(req.ConnectType, deviceCode, req.ChannelId, req.IPAddress, req.Port, req.COMPort, req.BaudRate) if validationErr != nil { return validationErr } if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } // MQTT 设备必须绑定通道(路由依赖 channel -> booth -> lot 坐标) if req.ConnectType == dao.ConnectTypeMQTT && req.ChannelId == 0 { return fmt.Errorf("MQTT 设备必须绑定通道") } if deviceCode != reader.DeviceCode { var duplicate dao.UHFReader if check := global.GVA_DB.Where("device_code = ? AND id <> ?", deviceCode, reader.ID).First(&duplicate); check.Error == nil { return fmt.Errorf("设备编码已存在: %s", deviceCode) } } if stopErr := StopDeviceHandler(reader.DeviceCode); stopErr != nil { return stopErr } updateData := map[string]interface{}{ "device_code": deviceCode, "device_name": req.DeviceName, "device_type": req.DeviceType, "connect_type": req.ConnectType, "ip_address": req.IPAddress, "port": req.Port, "com_port": req.COMPort, "baud_rate": req.BaudRate, "is_active": req.IsActive, "description": req.Description, "channel_id": req.ChannelId, "parking_lot_id": parkingLotID, } if err := global.GVA_DB.Model(&reader).Updates(updateData).Error; err != nil { return err } reader.DeviceCode = deviceCode reader.ConnectType = req.ConnectType reader.IPAddress = req.IPAddress reader.Port = req.Port reader.COMPort = req.COMPort reader.BaudRate = req.BaudRate reader.IsActive = req.IsActive reader.ChannelID = uint(req.ChannelId) reader.ParkingLotID = parkingLotID if req.IsActive && req.ConnectType != dao.ConnectTypeMQTT { if err := StartDeviceHandler(&reader); err != nil { _ = global.GVA_DB.Model(&reader).Update("status", "offline").Error return fmt.Errorf("设备更新成功但启动失败: %w", err) } return nil } return global.GVA_DB.Model(&reader).Update("status", "offline").Error } // ConnectedDevice 连接设备 func (s *uhfService) ConnectedDevice(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if reader.ConnectType == dao.ConnectTypeMQTT { return fmt.Errorf("MQTT 设备由设备主动连接,无需手动连接") } return StartDeviceHandler(&reader) } // GetReaderByCode 根据设备编码获取设备 func (s *uhfService) GetReaderByCode(code string) (response.UHFReaderResponse, error) { var reader dao.UHFReader result := global.GVA_DB.Where("device_code = ?", code).First(&reader) if result.RowsAffected == 0 { return response.UHFReaderResponse{}, fmt.Errorf("设备不存在") } return response.UHFReaderResponse{ ID: reader.ID, DeviceCode: reader.DeviceCode, DeviceName: reader.DeviceName, ConnectType: reader.ConnectType, IPAddress: reader.IPAddress, Port: reader.Port, COMPort: reader.COMPort, BaudRate: reader.BaudRate, Status: reader.Status, IsActive: reader.IsActive, Description: reader.Description, ParkingLotID: reader.ParkingLotID, CreatedAt: reader.CreatedAt, UpdatedAt: reader.UpdatedAt, }, nil } func (s *uhfService) QueryAllReaders() ([]dao.UHFReader, error) { var readers []dao.UHFReader err := global.GVA_DB.Find(&readers).Error if err != nil { return readers, err } return readers, nil } // ListReaders 分页查询设备列表 func (s *uhfService) ListReaders(req request.UHFReaderQuery) ([]dao.UHFReader, int64, error) { var readers []dao.UHFReader var total int64 query := global.GVA_DB.Model(&dao.UHFReader{}).Preload("Channel").Preload("ParkingLot") if req.DeviceCode != "" { query = query.Where("device_code LIKE ?", "%"+req.DeviceCode+"%") } if req.DeviceName != "" { query = query.Where("device_name LIKE ?", "%"+req.DeviceName+"%") } if req.Status != "" { query = query.Where("status = ?", req.Status) } if req.ConnectType != "" { query = query.Where("connect_type = ?", req.ConnectType) } if req.ParkingLotID != 0 { query = query.Where("parking_lot_id = ?", req.ParkingLotID) } err := query.Count(&total).Error if err != nil { return nil, 0, err } limit := req.PageSize offset := (req.Page - 1) * req.PageSize err = query.Limit(limit).Offset(offset).Order("created_at DESC").Find(&readers).Error if err != nil { return nil, 0, err } return readers, total, nil } // DeleteReader 删除设备 func (s *uhfService) DeleteReader(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if err := StopDeviceHandler(reader.DeviceCode); err != nil { return err } return global.GVA_DB.Delete(&reader).Error } // ConnectReader 连接设备 func (s *uhfService) ConnectReader(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if !reader.IsActive { return fmt.Errorf("设备未启用") } if reader.ConnectType == dao.ConnectTypeMQTT { return fmt.Errorf("MQTT 设备由设备主动连接,无需手动连接") } if err := StartDeviceHandler(&reader); err != nil { _ = global.GVA_DB.Model(&reader).Update("status", "offline").Error return err } fmt.Printf("设备[%s]连接成功\n", reader.DeviceCode) return nil } // DisconnectReader 断开设备连接 func (s *uhfService) DisconnectReader(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if reader.ConnectType == dao.ConnectTypeMQTT { return fmt.Errorf("MQTT设备由设备主动连接,不能手动断开") } if err := StopDeviceHandler(reader.DeviceCode); err != nil { return err } err := global.GVA_DB.Model(&reader).Update("status", "offline").Error if err != nil { return err } fmt.Printf("设备[%s]断开连接\n", reader.DeviceCode) return nil } // ReportTag 客户端上报标签数据 func (s *uhfService) ReportTag(deviceCode string, tag string) error { var reader dao.UHFReader result := global.GVA_DB.Where("device_code = ?", deviceCode).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } fmt.Printf("📶 收到设备 %s 上报的标签: %s\n", deviceCode, tag) return nil } // SetReaderPower 设置功率(模拟实现) func (s *uhfService) SetReaderPower(id uint, power int) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if reader.Status != "online" { return fmt.Errorf("设备未连接") } fmt.Printf("设备[%s]功率设置为: %ddBm\n", reader.DeviceCode, power) return nil } // StartInventory 启动读卡(模拟实现) func (s *uhfService) StartInventory(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } if reader.Status != "online" { return fmt.Errorf("设备未连接") } fmt.Printf("设备[%s]已启动读卡模式\n", reader.DeviceCode) return nil } // StopInventory 停止读卡(模拟实现) func (s *uhfService) StopInventory(id uint) error { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return fmt.Errorf("设备不存在") } fmt.Printf("设备[%s]已停止读卡\n", reader.DeviceCode) return nil } // ReadTag 读取标签(模拟实现) func (s *uhfService) ReadTag(id uint) (string, error) { var reader dao.UHFReader result := global.GVA_DB.Where("id = ?", id).First(&reader) if result.RowsAffected == 0 { return "", fmt.Errorf("设备不存在") } if reader.Status != "online" { return "", fmt.Errorf("设备未连接") } return "", nil }