passage.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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)
  105. case "out":
  106. return s.handleExit(req, identifier)
  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) (*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. })
  127. if err != nil {
  128. return nil, err
  129. }
  130. passageMutex.Lock()
  131. passageStatus[identifier] = true
  132. passageMutex.Unlock()
  133. result := &common.PassageResult{
  134. Success: true,
  135. Message: "入场成功",
  136. Direction: "in",
  137. PlateNumber: resp.Data.PlateNumber,
  138. RFIDTag: req.RFIDTag,
  139. EntryTime: resp.Data.EntryTime,
  140. BusinessCompleted: true,
  141. GateStatus: "opening",
  142. DeviceCode: req.DeviceCode,
  143. }
  144. fmt.Printf("🟢 入口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  145. if err := s.OpenGateByDeviceCode(req.DeviceCode, defaultGateValidTime); err != nil {
  146. result.Success = false
  147. result.Message = "入场记录已创建,但开闸失败"
  148. result.GateStatus = "failed"
  149. return result, gateOperationError("开闸", req.DeviceCode, err)
  150. }
  151. result.GateOpened = true
  152. result.GateStatus = "opened"
  153. return result, nil
  154. }
  155. // handleExit 处理出场(使用新的收费流程 ExitConfirm)
  156. func (s *PassageService) handleExit(req common.PassageRequest, identifier string) (*common.PassageResult, error) {
  157. // UHF 自动出场默认走免密;人工入口会显式传入支付参数。
  158. paymentMethod := req.PaymentMethod
  159. if paymentMethod == "" {
  160. paymentMethod = "free"
  161. }
  162. vehicleSvc := &vehicle.VehicleService{}
  163. confirmReq := request.ExitConfirmRequest{
  164. SessionID: req.SessionID,
  165. TicketNo: req.TicketNo,
  166. PlateNumber: req.PlateNumber,
  167. RFIDTag: req.RFIDTag,
  168. PaymentEntry: req.PaymentEntry,
  169. PaymentMethod: paymentMethod,
  170. PaidAmount: req.PaidAmount,
  171. }
  172. resp, err := vehicleSvc.ExitConfirm(confirmReq, req.OperatorID)
  173. if err != nil {
  174. return nil, err
  175. }
  176. passageMutex.Lock()
  177. passageStatus[identifier] = false
  178. passageMutex.Unlock()
  179. result := &common.PassageResult{
  180. Success: true,
  181. Message: "出场成功",
  182. Direction: "out",
  183. PlateNumber: resp.PlateNumber,
  184. RFIDTag: req.RFIDTag,
  185. Fee: resp.Fee,
  186. StayTime: resp.StayTime,
  187. EntryTime: resp.EntryTime,
  188. ExitTime: resp.ExitTime,
  189. BusinessCompleted: true,
  190. GateStatus: "opening",
  191. DeviceCode: req.DeviceCode,
  192. }
  193. fmt.Printf("🔴 出口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
  194. if err := s.OpenGateByDeviceCode(req.DeviceCode, defaultGateValidTime); err != nil {
  195. result.Success = false
  196. result.Message = "出场结算已完成,但开闸失败"
  197. result.GateStatus = "failed"
  198. return result, gateOperationError("开闸", req.DeviceCode, err)
  199. }
  200. result.GateOpened = true
  201. result.GateStatus = "opened"
  202. return result, nil
  203. }
  204. // SetEpcExit 手动重置离场状态
  205. func SetEpcExit(identifier string) {
  206. passageMutex.Lock()
  207. defer passageMutex.Unlock()
  208. passageStatus[identifier] = false
  209. fmt.Printf("🚙 手动重置离场 | 标识: %s\n", identifier)
  210. }