passage.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package parking
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. "time"
  7. "wails-app/internal/dao"
  8. "wails-app/internal/global"
  9. common "wails-app/internal/model/common"
  10. "wails-app/internal/model/vehicle/request"
  11. "wails-app/internal/service/vehicle"
  12. )
  13. // PassageService 统一的进出场服务(所有触发方式共用:RFID、摄像头、手动录入)
  14. type PassageService struct{}
  15. // ===================== 防抖与状态 =====================
  16. var (
  17. passageLastTime = make(map[string]int64)
  18. passageStatus = make(map[string]bool) // true=在场内
  19. passageMutex sync.Mutex
  20. passageDebounceSec = int64(3) // 3秒防抖
  21. )
  22. // getIdentifier 从请求中提取唯一标识(优先 RFID,其次车牌)
  23. func getIdentifier(req common.PassageRequest) string {
  24. if req.RFIDTag != "" {
  25. return req.RFIDTag
  26. }
  27. return req.PlateNumber
  28. }
  29. // HandlePassage 统一的进出场处理入口
  30. // 调用方只需传入车辆标识 + 设备编码,其余逻辑全部在此处理:
  31. //
  32. // 查询设备/通道 → 查询车辆 → 黑名单检查 → 临时车规则 → 进场/出场 → 开闸
  33. func (s *PassageService) HandlePassage(req common.PassageRequest) (*common.PassageResult, error) {
  34. identifier := getIdentifier(req)
  35. if identifier == "" {
  36. return nil, fmt.Errorf("缺少车辆标识(RFIDTag 或 PlateNumber)")
  37. }
  38. // ===================== 1. 防抖 =====================
  39. passageMutex.Lock()
  40. if last, ok := passageLastTime[identifier]; ok && time.Now().Unix()-last < passageDebounceSec {
  41. passageMutex.Unlock()
  42. return nil, fmt.Errorf("防抖中: %s", identifier)
  43. }
  44. passageLastTime[identifier] = time.Now().Unix()
  45. passageMutex.Unlock()
  46. // ===================== 2. 查询设备 + 通道 =====================
  47. var device dao.UHFReader
  48. if err := global.GVA_DB.Preload("Channel").First(&device, "device_code = ?", req.DeviceCode).Error; err != nil {
  49. return nil, fmt.Errorf("设备不存在: %s", req.DeviceCode)
  50. }
  51. if !device.IsActive {
  52. return nil, fmt.Errorf("设备已停用: %s", req.DeviceCode)
  53. }
  54. if device.Channel == nil {
  55. return nil, fmt.Errorf("设备未绑定通道: %s", req.DeviceCode)
  56. }
  57. channel := device.Channel
  58. direction := channel.Direction
  59. if direction == "inout" {
  60. if req.Direction != "in" && req.Direction != "out" {
  61. return nil, errors.New("双向通道必须明确本次通行方向")
  62. }
  63. direction = req.Direction
  64. } else if req.Direction != "" && req.Direction != direction {
  65. return nil, fmt.Errorf("所选设备属于%s通道,不能执行%s操作", direction, req.Direction)
  66. }
  67. allowTemporary := channel.AllowTemporary
  68. // ===================== 3. 查询车辆 =====================
  69. vehicleSvc := &vehicle.VehicleService{}
  70. shortlistSvc := &vehicle.ShortlistService{}
  71. v, err := vehicleSvc.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  72. isTempVehicle := false
  73. if err != nil || v.ID == 0 {
  74. isTempVehicle = true
  75. } else if v.VehicleType != nil {
  76. isTempVehicle = v.VehicleType.IsSystem
  77. } else {
  78. isTempVehicle = true
  79. }
  80. // ===================== 4. 黑名单检查 =====================
  81. isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag)
  82. if isBlack {
  83. fmt.Printf("🚫 禁止通行 | 车辆在黑名单中 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  84. return nil, fmt.Errorf("车辆在黑名单中")
  85. }
  86. // ===================== 5. 临时车规则 =====================
  87. if isTempVehicle && !allowTemporary {
  88. fmt.Printf("🚫 禁止通行 | 临时车不允许此通道 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  89. return nil, fmt.Errorf("临时车不允许此通道")
  90. }
  91. // ===================== 6. 根据方向处理 =====================
  92. switch direction {
  93. case "in":
  94. parkingLotID := channel.ParkingLotID
  95. if parkingLotID == 0 {
  96. parkingLotID = req.ParkingLotID
  97. }
  98. if parkingLotID == 0 {
  99. return nil, errors.New("通道未配置停车场")
  100. }
  101. if req.ParkingLotID != 0 && req.ParkingLotID != parkingLotID {
  102. return nil, errors.New("所选停车场与通道配置不一致")
  103. }
  104. return s.handleEntry(req, identifier, parkingLotID, channel, &device)
  105. case "out":
  106. return s.handleExit(req, identifier, channel, &device)
  107. default:
  108. return nil, fmt.Errorf("未知通道方向: %s", direction)
  109. }
  110. }
  111. // handleEntry 处理入场
  112. func (s *PassageService) handleEntry(req common.PassageRequest, identifier string, parkingLotID uint, channel *dao.Channel, device *dao.UHFReader) (*common.PassageResult, error) {
  113. passageMutex.Lock()
  114. canIn := !passageStatus[identifier]
  115. passageMutex.Unlock()
  116. if !canIn {
  117. return nil, fmt.Errorf("车辆已在场内: %s", identifier)
  118. }
  119. // 先创建停车会话和数字票,避免业务失败时车辆已经放行。
  120. vehicleSvc := &vehicle.VehicleService{}
  121. resp, err := vehicleSvc.VehicleEntry(request.VehicleEntry{
  122. PlateNumber: req.PlateNumber,
  123. RFIDTag: req.RFIDTag,
  124. ParkingLotID: parkingLotID,
  125. EntryImage: req.Image,
  126. EntryChannelID: channel.ID,
  127. EntryChannelCode: channel.ChannelCode,
  128. EntryChannelName: channel.ChannelName,
  129. EntryDeviceCode: req.DeviceCode,
  130. EntryDeviceName: device.DeviceName,
  131. })
  132. if err != nil {
  133. return nil, err
  134. }
  135. passageMutex.Lock()
  136. passageStatus[identifier] = true
  137. passageMutex.Unlock()
  138. result := &common.PassageResult{
  139. Success: true,
  140. Message: "入场成功",
  141. Direction: "in",
  142. PlateNumber: resp.Data.PlateNumber,
  143. RFIDTag: req.RFIDTag,
  144. EntryTime: resp.Data.EntryTime,
  145. BusinessCompleted: true,
  146. GateStatus: "opening",
  147. DeviceCode: req.DeviceCode,
  148. }
  149. fmt.Printf("🟢 入口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  150. if err := s.OpenGateByDeviceCode(req.DeviceCode, defaultGateValidTime); err != nil {
  151. result.Success = false
  152. result.Message = "入场记录已创建,但开闸失败"
  153. result.GateStatus = "failed"
  154. return result, gateOperationError("开闸", req.DeviceCode, err)
  155. }
  156. result.GateOpened = true
  157. result.GateStatus = "opened"
  158. return result, nil
  159. }
  160. // handleExit 处理出场(使用新的收费流程 ExitConfirm)
  161. func (s *PassageService) handleExit(req common.PassageRequest, identifier string, channel *dao.Channel, device *dao.UHFReader) (*common.PassageResult, error) {
  162. // UHF 自动出场默认走免密;人工入口会显式传入支付参数。
  163. paymentMethod := req.PaymentMethod
  164. if paymentMethod == "" {
  165. paymentMethod = "free"
  166. }
  167. vehicleSvc := &vehicle.VehicleService{}
  168. confirmReq := request.ExitConfirmRequest{
  169. SessionID: req.SessionID,
  170. TicketNo: req.TicketNo,
  171. PlateNumber: req.PlateNumber,
  172. RFIDTag: req.RFIDTag,
  173. PaymentEntry: req.PaymentEntry,
  174. PaymentMethod: paymentMethod,
  175. PaidAmount: req.PaidAmount,
  176. ExitChannelID: channel.ID,
  177. ExitChannelCode: channel.ChannelCode,
  178. ExitChannelName: channel.ChannelName,
  179. ExitDeviceCode: req.DeviceCode,
  180. ExitDeviceName: device.DeviceName,
  181. }
  182. resp, err := vehicleSvc.ExitConfirm(confirmReq, req.OperatorID)
  183. if err != nil {
  184. return nil, err
  185. }
  186. passageMutex.Lock()
  187. passageStatus[identifier] = false
  188. passageMutex.Unlock()
  189. result := &common.PassageResult{
  190. Success: true,
  191. Message: "出场成功",
  192. Direction: "out",
  193. PlateNumber: resp.PlateNumber,
  194. RFIDTag: req.RFIDTag,
  195. Fee: resp.Fee,
  196. StayTime: resp.StayTime,
  197. EntryTime: resp.EntryTime,
  198. ExitTime: resp.ExitTime,
  199. BusinessCompleted: true,
  200. GateStatus: "opening",
  201. DeviceCode: req.DeviceCode,
  202. }
  203. fmt.Printf("🔴 出口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  204. if err := s.OpenGateByDeviceCode(req.DeviceCode, defaultGateValidTime); err != nil {
  205. result.Success = false
  206. result.Message = "出场结算已完成,但开闸失败"
  207. result.GateStatus = "failed"
  208. return result, gateOperationError("开闸", req.DeviceCode, err)
  209. }
  210. result.GateOpened = true
  211. result.GateStatus = "opened"
  212. return result, nil
  213. }
  214. // SetEpcExit 手动重置离场状态
  215. func SetEpcExit(identifier string) {
  216. passageMutex.Lock()
  217. defer passageMutex.Unlock()
  218. passageStatus[identifier] = false
  219. fmt.Printf("🚙 手动重置离场 | 标识: %s\n", identifier)
  220. }