gate.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package parking
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. "time"
  7. "go.uber.org/zap"
  8. "wails-app/internal/dao"
  9. "wails-app/internal/global"
  10. incidentService "wails-app/internal/modules/incident/service"
  11. )
  12. const defaultGateValidTime byte = 2
  13. var incidentSvc = incidentService.NewIncidentService()
  14. // GateCommandContext 道闸指令上下文(用于指令流水审计)。
  15. type GateCommandContext struct {
  16. Source string // passage / manual / test
  17. OperatorID uint
  18. SessionID uint
  19. }
  20. // GateController 统一道闸设备控制接口,具体协议由设备模块实现。
  21. type GateController interface {
  22. OpenGate(deviceCode string, validTime byte) error
  23. CloseGate(deviceCode string, validTime byte) error
  24. IsGateConnected(deviceCode string) bool
  25. }
  26. // GateDeviceOption 是工作台可见的安全设备信息,不包含串口、IP 等连接参数。
  27. type GateDeviceOption struct {
  28. DeviceCode string `json:"device_code"`
  29. DeviceName string `json:"device_name"`
  30. ChannelID uint `json:"channel_id"`
  31. ChannelCode string `json:"channel_code"`
  32. ChannelName string `json:"channel_name"`
  33. Direction string `json:"direction"`
  34. ParkingLotID uint `json:"parking_lot_id"`
  35. Connected bool `json:"connected"`
  36. Simulated bool `json:"simulated"`
  37. }
  38. // GateRuntimeStatus 表示统一控制器中的设备实时状态。
  39. type GateRuntimeStatus struct {
  40. Connected bool
  41. Simulated bool
  42. }
  43. // SimulatedGateController 用于无硬件环境联调,只记录指令,不访问串口或网络设备。
  44. type SimulatedGateController struct {
  45. mu sync.RWMutex
  46. lastAction map[string]string
  47. }
  48. // NewSimulatedGateController 创建模拟道闸控制器。
  49. func NewSimulatedGateController() *SimulatedGateController {
  50. return &SimulatedGateController{lastAction: make(map[string]string)}
  51. }
  52. func (c *SimulatedGateController) record(deviceCode, action string) error {
  53. if deviceCode == "" {
  54. return errors.New("道闸设备编码不能为空")
  55. }
  56. c.mu.Lock()
  57. c.lastAction[deviceCode] = action
  58. c.mu.Unlock()
  59. if global.GVA_LOG != nil {
  60. global.GVA_LOG.Warn("模拟道闸指令", zap.String("device_code", deviceCode), zap.String("action", action), zap.Time("time", time.Now()))
  61. }
  62. return nil
  63. }
  64. // OpenGate 模拟开闸。
  65. func (c *SimulatedGateController) OpenGate(deviceCode string, _ byte) error {
  66. return c.record(deviceCode, "open")
  67. }
  68. // CloseGate 模拟关闸。
  69. func (c *SimulatedGateController) CloseGate(deviceCode string, _ byte) error {
  70. return c.record(deviceCode, "close")
  71. }
  72. // IsGateConnected 在模拟模式下将所有有效设备编码视为在线。
  73. func (c *SimulatedGateController) IsGateConnected(deviceCode string) bool {
  74. return deviceCode != ""
  75. }
  76. func (c *SimulatedGateController) isSimulator() bool {
  77. return true
  78. }
  79. var (
  80. gateControllerMu sync.RWMutex
  81. gateController GateController
  82. )
  83. // SetGateController 注入设备控制实现,避免停车业务反向依赖具体设备协议。
  84. func SetGateController(controller GateController) {
  85. gateControllerMu.Lock()
  86. defer gateControllerMu.Unlock()
  87. gateController = controller
  88. }
  89. func currentGateController() GateController {
  90. gateControllerMu.RLock()
  91. defer gateControllerMu.RUnlock()
  92. return gateController
  93. }
  94. // GetGateRuntimeStatus 返回设备在当前控制器中的实时连接状态。
  95. func GetGateRuntimeStatus(deviceCode string) GateRuntimeStatus {
  96. controller := currentGateController()
  97. if controller == nil || deviceCode == "" {
  98. return GateRuntimeStatus{}
  99. }
  100. status := GateRuntimeStatus{Connected: controller.IsGateConnected(deviceCode)}
  101. if simulator, ok := controller.(interface{ isSimulator() bool }); ok {
  102. status.Simulated = simulator.isSimulator()
  103. }
  104. return status
  105. }
  106. // OpenGateByDeviceCode 通过统一控制器开闸。
  107. func (s *PassageService) OpenGateByDeviceCode(deviceCode string, validTime byte) error {
  108. if deviceCode == "" {
  109. return errors.New("道闸设备编码不能为空")
  110. }
  111. controller := currentGateController()
  112. if controller == nil {
  113. return errors.New("道闸控制器未初始化")
  114. }
  115. if validTime == 0 {
  116. validTime = defaultGateValidTime
  117. }
  118. return controller.OpenGate(deviceCode, validTime)
  119. }
  120. // CloseGateByDeviceCode 通过统一控制器关闸。
  121. func (s *PassageService) CloseGateByDeviceCode(deviceCode string, validTime byte) error {
  122. if deviceCode == "" {
  123. return errors.New("道闸设备编码不能为空")
  124. }
  125. controller := currentGateController()
  126. if controller == nil {
  127. return errors.New("道闸控制器未初始化")
  128. }
  129. if validTime == 0 {
  130. validTime = defaultGateValidTime
  131. }
  132. return controller.CloseGate(deviceCode, validTime)
  133. }
  134. // OpenGateWithContext 开闸并记录指令流水(所有开闸路径的审计入口),返回流水 ID。
  135. func (s *PassageService) OpenGateWithContext(ctx GateCommandContext, deviceCode string, validTime byte) (uint, error) {
  136. return s.runGateCommand(ctx, "open", deviceCode, validTime)
  137. }
  138. // CloseGateWithContext 关闸并记录指令流水,返回流水 ID。
  139. func (s *PassageService) CloseGateWithContext(ctx GateCommandContext, deviceCode string, validTime byte) (uint, error) {
  140. return s.runGateCommand(ctx, "close", deviceCode, validTime)
  141. }
  142. // runGateCommand 执行道闸指令并写指令流水(best-effort,流水失败不影响指令结果)。
  143. func (s *PassageService) runGateCommand(ctx GateCommandContext, action, deviceCode string, validTime byte) (uint, error) {
  144. if ctx.Source == "" {
  145. ctx.Source = "passage"
  146. }
  147. start := time.Now()
  148. var err error
  149. if action == "open" {
  150. err = s.OpenGateByDeviceCode(deviceCode, validTime)
  151. } else {
  152. err = s.CloseGateByDeviceCode(deviceCode, validTime)
  153. }
  154. rec := incidentService.GateCommandRecord{
  155. DeviceCode: deviceCode,
  156. Action: action,
  157. Source: ctx.Source,
  158. OperatorID: ctx.OperatorID,
  159. SessionID: ctx.SessionID,
  160. DurationMs: time.Since(start).Milliseconds(),
  161. }
  162. if err != nil {
  163. rec.Result = incidentService.CommandFailed
  164. rec.ErrorMessage = err.Error()
  165. } else {
  166. rec.Result = incidentService.CommandSuccess
  167. }
  168. // 设备名称快照(查询失败不影响指令结果)
  169. var device dao.UHFReader
  170. if global.GVA_DB != nil {
  171. if qerr := global.GVA_DB.Where("device_code = ?", deviceCode).First(&device).Error; qerr == nil {
  172. rec.DeviceName = device.DeviceName
  173. }
  174. }
  175. return incidentSvc.RecordGateCommand(rec), err
  176. }
  177. // ListGateDevices 返回已启用且已绑定通道的道闸设备。
  178. func (s *PassageService) ListGateDevices() ([]GateDeviceOption, error) {
  179. if global.GVA_DB == nil {
  180. return nil, errors.New("数据库未初始化")
  181. }
  182. var readers []dao.UHFReader
  183. if err := global.GVA_DB.Preload("Channel").
  184. Where("is_active = ? AND channel_id > 0", true).
  185. Order("device_name ASC").Find(&readers).Error; err != nil {
  186. return nil, err
  187. }
  188. options := make([]GateDeviceOption, 0, len(readers))
  189. for _, reader := range readers {
  190. if reader.Channel == nil {
  191. continue
  192. }
  193. parkingLotID := reader.Channel.ParkingLotID
  194. if parkingLotID == 0 {
  195. parkingLotID = reader.ParkingLotID
  196. }
  197. runtimeStatus := GetGateRuntimeStatus(reader.DeviceCode)
  198. options = append(options, GateDeviceOption{
  199. DeviceCode: reader.DeviceCode, DeviceName: reader.DeviceName,
  200. ChannelID: reader.ChannelID, ChannelCode: reader.Channel.ChannelCode,
  201. ChannelName: reader.Channel.ChannelName, Direction: reader.Channel.Direction,
  202. ParkingLotID: parkingLotID,
  203. Connected: runtimeStatus.Connected,
  204. Simulated: runtimeStatus.Simulated,
  205. })
  206. }
  207. return options, nil
  208. }
  209. func gateOperationError(action, deviceCode string, err error) error {
  210. return fmt.Errorf("道闸%s失败(设备 %s): %w", action, deviceCode, err)
  211. }