| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- package parking
- import (
- "errors"
- "fmt"
- "sync"
- "time"
- "go.uber.org/zap"
- "wails-app/internal/dao"
- "wails-app/internal/global"
- incidentService "wails-app/internal/modules/incident/service"
- )
- const defaultGateValidTime byte = 2
- var incidentSvc = incidentService.NewIncidentService()
- // GateCommandContext 道闸指令上下文(用于指令流水审计)。
- type GateCommandContext struct {
- Source string // passage / manual / test
- OperatorID uint
- SessionID uint
- }
- // GateController 统一道闸设备控制接口,具体协议由设备模块实现。
- type GateController interface {
- OpenGate(deviceCode string, validTime byte) error
- CloseGate(deviceCode string, validTime byte) error
- IsGateConnected(deviceCode string) bool
- }
- // GateDeviceOption 是工作台可见的安全设备信息,不包含串口、IP 等连接参数。
- type GateDeviceOption struct {
- DeviceCode string `json:"device_code"`
- DeviceName string `json:"device_name"`
- ChannelID uint `json:"channel_id"`
- ChannelCode string `json:"channel_code"`
- ChannelName string `json:"channel_name"`
- Direction string `json:"direction"`
- ParkingLotID uint `json:"parking_lot_id"`
- Connected bool `json:"connected"`
- Simulated bool `json:"simulated"`
- }
- // GateRuntimeStatus 表示统一控制器中的设备实时状态。
- type GateRuntimeStatus struct {
- Connected bool
- Simulated bool
- }
- // SimulatedGateController 用于无硬件环境联调,只记录指令,不访问串口或网络设备。
- type SimulatedGateController struct {
- mu sync.RWMutex
- lastAction map[string]string
- }
- // NewSimulatedGateController 创建模拟道闸控制器。
- func NewSimulatedGateController() *SimulatedGateController {
- return &SimulatedGateController{lastAction: make(map[string]string)}
- }
- func (c *SimulatedGateController) record(deviceCode, action string) error {
- if deviceCode == "" {
- return errors.New("道闸设备编码不能为空")
- }
- c.mu.Lock()
- c.lastAction[deviceCode] = action
- c.mu.Unlock()
- if global.GVA_LOG != nil {
- global.GVA_LOG.Warn("模拟道闸指令", zap.String("device_code", deviceCode), zap.String("action", action), zap.Time("time", time.Now()))
- }
- return nil
- }
- // OpenGate 模拟开闸。
- func (c *SimulatedGateController) OpenGate(deviceCode string, _ byte) error {
- return c.record(deviceCode, "open")
- }
- // CloseGate 模拟关闸。
- func (c *SimulatedGateController) CloseGate(deviceCode string, _ byte) error {
- return c.record(deviceCode, "close")
- }
- // IsGateConnected 在模拟模式下将所有有效设备编码视为在线。
- func (c *SimulatedGateController) IsGateConnected(deviceCode string) bool {
- return deviceCode != ""
- }
- func (c *SimulatedGateController) isSimulator() bool {
- return true
- }
- var (
- gateControllerMu sync.RWMutex
- gateController GateController
- )
- // SetGateController 注入设备控制实现,避免停车业务反向依赖具体设备协议。
- func SetGateController(controller GateController) {
- gateControllerMu.Lock()
- defer gateControllerMu.Unlock()
- gateController = controller
- }
- func currentGateController() GateController {
- gateControllerMu.RLock()
- defer gateControllerMu.RUnlock()
- return gateController
- }
- // GetGateRuntimeStatus 返回设备在当前控制器中的实时连接状态。
- func GetGateRuntimeStatus(deviceCode string) GateRuntimeStatus {
- controller := currentGateController()
- if controller == nil || deviceCode == "" {
- return GateRuntimeStatus{}
- }
- status := GateRuntimeStatus{Connected: controller.IsGateConnected(deviceCode)}
- if simulator, ok := controller.(interface{ isSimulator() bool }); ok {
- status.Simulated = simulator.isSimulator()
- }
- return status
- }
- // OpenGateByDeviceCode 通过统一控制器开闸。
- func (s *PassageService) OpenGateByDeviceCode(deviceCode string, validTime byte) error {
- if deviceCode == "" {
- return errors.New("道闸设备编码不能为空")
- }
- controller := currentGateController()
- if controller == nil {
- return errors.New("道闸控制器未初始化")
- }
- if validTime == 0 {
- validTime = defaultGateValidTime
- }
- return controller.OpenGate(deviceCode, validTime)
- }
- // CloseGateByDeviceCode 通过统一控制器关闸。
- func (s *PassageService) CloseGateByDeviceCode(deviceCode string, validTime byte) error {
- if deviceCode == "" {
- return errors.New("道闸设备编码不能为空")
- }
- controller := currentGateController()
- if controller == nil {
- return errors.New("道闸控制器未初始化")
- }
- if validTime == 0 {
- validTime = defaultGateValidTime
- }
- return controller.CloseGate(deviceCode, validTime)
- }
- // OpenGateWithContext 开闸并记录指令流水(所有开闸路径的审计入口),返回流水 ID。
- func (s *PassageService) OpenGateWithContext(ctx GateCommandContext, deviceCode string, validTime byte) (uint, error) {
- return s.runGateCommand(ctx, "open", deviceCode, validTime)
- }
- // CloseGateWithContext 关闸并记录指令流水,返回流水 ID。
- func (s *PassageService) CloseGateWithContext(ctx GateCommandContext, deviceCode string, validTime byte) (uint, error) {
- return s.runGateCommand(ctx, "close", deviceCode, validTime)
- }
- // runGateCommand 执行道闸指令并写指令流水(best-effort,流水失败不影响指令结果)。
- func (s *PassageService) runGateCommand(ctx GateCommandContext, action, deviceCode string, validTime byte) (uint, error) {
- if ctx.Source == "" {
- ctx.Source = "passage"
- }
- start := time.Now()
- var err error
- if action == "open" {
- err = s.OpenGateByDeviceCode(deviceCode, validTime)
- } else {
- err = s.CloseGateByDeviceCode(deviceCode, validTime)
- }
- rec := incidentService.GateCommandRecord{
- DeviceCode: deviceCode,
- Action: action,
- Source: ctx.Source,
- OperatorID: ctx.OperatorID,
- SessionID: ctx.SessionID,
- DurationMs: time.Since(start).Milliseconds(),
- }
- if err != nil {
- rec.Result = incidentService.CommandFailed
- rec.ErrorMessage = err.Error()
- } else {
- rec.Result = incidentService.CommandSuccess
- }
- // 设备名称快照(查询失败不影响指令结果)
- var device dao.UHFReader
- if global.GVA_DB != nil {
- if qerr := global.GVA_DB.Where("device_code = ?", deviceCode).First(&device).Error; qerr == nil {
- rec.DeviceName = device.DeviceName
- }
- }
- return incidentSvc.RecordGateCommand(rec), err
- }
- // ListGateDevices 返回已启用且已绑定通道的道闸设备。
- func (s *PassageService) ListGateDevices() ([]GateDeviceOption, error) {
- if global.GVA_DB == nil {
- return nil, errors.New("数据库未初始化")
- }
- var readers []dao.UHFReader
- if err := global.GVA_DB.Preload("Channel").
- Where("is_active = ? AND channel_id > 0", true).
- Order("device_name ASC").Find(&readers).Error; err != nil {
- return nil, err
- }
- options := make([]GateDeviceOption, 0, len(readers))
- for _, reader := range readers {
- if reader.Channel == nil {
- continue
- }
- parkingLotID := reader.Channel.ParkingLotID
- if parkingLotID == 0 {
- parkingLotID = reader.ParkingLotID
- }
- runtimeStatus := GetGateRuntimeStatus(reader.DeviceCode)
- options = append(options, GateDeviceOption{
- DeviceCode: reader.DeviceCode, DeviceName: reader.DeviceName,
- ChannelID: reader.ChannelID, ChannelCode: reader.Channel.ChannelCode,
- ChannelName: reader.Channel.ChannelName, Direction: reader.Channel.Direction,
- ParkingLotID: parkingLotID,
- Connected: runtimeStatus.Connected,
- Simulated: runtimeStatus.Simulated,
- })
- }
- return options, nil
- }
- func gateOperationError(action, deviceCode string, err error) error {
- return fmt.Errorf("道闸%s失败(设备 %s): %w", action, deviceCode, err)
- }
|