|
|
@@ -0,0 +1,192 @@
|
|
|
+package parking
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+ "wails-app/internal/dao"
|
|
|
+ "wails-app/internal/global"
|
|
|
+ common "wails-app/internal/model/common"
|
|
|
+ "wails-app/internal/model/vehicle/request"
|
|
|
+ "wails-app/internal/service/vehicle"
|
|
|
+)
|
|
|
+
|
|
|
+// GateOpener 道闸控制函数(由 UHF 模块在初始化时注入,避免循环依赖)
|
|
|
+var GateOpener func(deviceCode string, validTime byte) error
|
|
|
+
|
|
|
+// PassageService 统一的进出场服务(所有触发方式共用:RFID、摄像头、手动录入)
|
|
|
+type PassageService struct{}
|
|
|
+
|
|
|
+// ===================== 防抖与状态 =====================
|
|
|
+var (
|
|
|
+ passageLastTime = make(map[string]int64)
|
|
|
+ passageStatus = make(map[string]bool) // true=在场内
|
|
|
+ passageMutex sync.Mutex
|
|
|
+ passageDebounceSec = int64(3) // 3秒防抖
|
|
|
+)
|
|
|
+
|
|
|
+// getIdentifier 从请求中提取唯一标识(优先 RFID,其次车牌)
|
|
|
+func getIdentifier(req common.PassageRequest) string {
|
|
|
+ if req.RFIDTag != "" {
|
|
|
+ return req.RFIDTag
|
|
|
+ }
|
|
|
+ return req.PlateNumber
|
|
|
+}
|
|
|
+
|
|
|
+// HandlePassage 统一的进出场处理入口
|
|
|
+// 调用方只需传入车辆标识 + 设备编码,其余逻辑全部在此处理:
|
|
|
+//
|
|
|
+// 查询设备/通道 → 查询车辆 → 黑名单检查 → 临时车规则 → 进场/出场 → 开闸
|
|
|
+func (s *PassageService) HandlePassage(req common.PassageRequest) (*common.PassageResult, error) {
|
|
|
+ identifier := getIdentifier(req)
|
|
|
+ if identifier == "" {
|
|
|
+ return nil, fmt.Errorf("缺少车辆标识(RFIDTag 或 PlateNumber)")
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 1. 防抖 =====================
|
|
|
+ passageMutex.Lock()
|
|
|
+ if last, ok := passageLastTime[identifier]; ok && time.Now().Unix()-last < passageDebounceSec {
|
|
|
+ passageMutex.Unlock()
|
|
|
+ return nil, fmt.Errorf("防抖中: %s", identifier)
|
|
|
+ }
|
|
|
+ passageLastTime[identifier] = time.Now().Unix()
|
|
|
+ passageMutex.Unlock()
|
|
|
+
|
|
|
+ // ===================== 2. 查询设备 + 通道 =====================
|
|
|
+ var device dao.UHFReader
|
|
|
+ if err := global.GVA_DB.Preload("Channel").First(&device, "device_code = ?", req.DeviceCode).Error; err != nil {
|
|
|
+ return nil, fmt.Errorf("设备不存在: %s", req.DeviceCode)
|
|
|
+ }
|
|
|
+ channel := device.Channel
|
|
|
+ direction := channel.Direction
|
|
|
+ allowTemporary := channel.AllowTemporary
|
|
|
+
|
|
|
+ // ===================== 3. 查询车辆 =====================
|
|
|
+ vehicleSvc := &vehicle.VehicleService{}
|
|
|
+ shortlistSvc := &vehicle.ShortlistService{}
|
|
|
+
|
|
|
+ v, err := vehicleSvc.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
|
|
|
+ isTempVehicle := false
|
|
|
+ if err != nil || v.ID == 0 {
|
|
|
+ isTempVehicle = true
|
|
|
+ } else if v.VehicleType != nil {
|
|
|
+ isTempVehicle = (v.VehicleTypeID == 1) // 1=临时车
|
|
|
+ } else {
|
|
|
+ isTempVehicle = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 4. 黑名单检查 =====================
|
|
|
+ isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.RFIDTag)
|
|
|
+ if isBlack {
|
|
|
+ fmt.Printf("🚫 禁止通行 | 车辆在黑名单中 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
|
|
|
+ return nil, fmt.Errorf("车辆在黑名单中")
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 5. 临时车规则 =====================
|
|
|
+ if isTempVehicle && !allowTemporary {
|
|
|
+ fmt.Printf("🚫 禁止通行 | 临时车不允许此通道 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
|
|
|
+ return nil, fmt.Errorf("临时车不允许此通道")
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 6. 根据方向处理 =====================
|
|
|
+ switch direction {
|
|
|
+ case "in":
|
|
|
+ return s.handleEntry(req, identifier, channel.ParkingLotID)
|
|
|
+ case "out":
|
|
|
+ return s.handleExit(req, identifier)
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("未知通道方向: %s", direction)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// handleEntry 处理入场
|
|
|
+func (s *PassageService) handleEntry(req common.PassageRequest, identifier string, parkingLotID uint) (*common.PassageResult, error) {
|
|
|
+ passageMutex.Lock()
|
|
|
+ canIn := !passageStatus[identifier]
|
|
|
+ passageMutex.Unlock()
|
|
|
+
|
|
|
+ if !canIn {
|
|
|
+ return nil, fmt.Errorf("车辆已在场内: %s", identifier)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("🟢 入口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
|
|
|
+
|
|
|
+ // 开闸
|
|
|
+ if GateOpener != nil {
|
|
|
+ if err := GateOpener(req.DeviceCode, 2); err != nil {
|
|
|
+ fmt.Printf("⚠️ 开闸失败: %v\n", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行入场
|
|
|
+ vehicleSvc := &vehicle.VehicleService{}
|
|
|
+ resp, err := vehicleSvc.VehicleEntry(request.VehicleEntry{
|
|
|
+ PlateNumber: req.PlateNumber,
|
|
|
+ RFIDTag: req.RFIDTag,
|
|
|
+ ParkingLotID: parkingLotID,
|
|
|
+ EntryImage: req.Image,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ passageMutex.Lock()
|
|
|
+ passageStatus[identifier] = true
|
|
|
+ passageMutex.Unlock()
|
|
|
+
|
|
|
+ return &common.PassageResult{
|
|
|
+ Success: true,
|
|
|
+ Message: "入场成功",
|
|
|
+ Direction: "in",
|
|
|
+ PlateNumber: resp.Data.PlateNumber,
|
|
|
+ RFIDTag: req.RFIDTag,
|
|
|
+ EntryTime: resp.Data.EntryTime,
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
+// handleExit 处理出场
|
|
|
+func (s *PassageService) handleExit(req common.PassageRequest, identifier string) (*common.PassageResult, error) {
|
|
|
+ fmt.Printf("🔴 出口开闸 | 设备:%s 车牌:%s RFID:%s\n", req.DeviceCode, req.PlateNumber, req.RFIDTag)
|
|
|
+
|
|
|
+ // 开闸
|
|
|
+ if GateOpener != nil {
|
|
|
+ if err := GateOpener(req.DeviceCode, 2); err != nil {
|
|
|
+ fmt.Printf("⚠️ 开闸失败: %v\n", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行出场
|
|
|
+ vehicleSvc := &vehicle.VehicleService{}
|
|
|
+ resp, err := vehicleSvc.VehicleExit(request.VehicleExit{
|
|
|
+ PlateNumber: req.PlateNumber,
|
|
|
+ RFIDTag: req.RFIDTag,
|
|
|
+ ExitImage: req.Image,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ passageMutex.Lock()
|
|
|
+ passageStatus[identifier] = false
|
|
|
+ passageMutex.Unlock()
|
|
|
+
|
|
|
+ return &common.PassageResult{
|
|
|
+ Success: true,
|
|
|
+ Message: "出场成功",
|
|
|
+ Direction: "out",
|
|
|
+ PlateNumber: req.PlateNumber,
|
|
|
+ RFIDTag: req.RFIDTag,
|
|
|
+ Fee: resp.Data.Fee,
|
|
|
+ StayTime: resp.Data.StayTime,
|
|
|
+ EntryTime: resp.Data.EntryTime,
|
|
|
+ ExitTime: resp.Data.ExitTime,
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
+// SetEpcExit 手动重置离场状态
|
|
|
+func SetEpcExit(identifier string) {
|
|
|
+ passageMutex.Lock()
|
|
|
+ defer passageMutex.Unlock()
|
|
|
+ passageStatus[identifier] = false
|
|
|
+ fmt.Printf("🚙 手动重置离场 | 标识: %s\n", identifier)
|
|
|
+}
|