进出场逻辑流程.md 10 KB

智慧停车 — 进出场逻辑流程

日期:2026-07-24

1. 触发方式总览

车辆到达/离开
  │
  ├─ RFID 自动识别  → PassageService.HandlePassage()  → 自动进出场
  ├─ 手动操作       → entryExit.vue                 → 手动进出场
  └─ 摄像头车牌识别  → (待实现 P0-5)

2. 入场流程

2.1 流程图

车辆到达入口
  │
  ├── 手动录入 ──────────────────────────────┐
  │   前端 entryExit.vue                       │
  │   输入车牌/RFID → POST /vehicle/entry       │
  │                                           │
  ├── RFID 自动识别 ─────────────────────────  │
  │   reader.go → ParseBuffer()               │
  │   → PassageService.HandlePassage()        │
  │   → 防抖检查(3秒去重)                     │
  │   → handleEntry()                         │
  │                                           │
  ▼                                           ▼
┌─────────────────────────────────────────────────┐
│              VehicleEntry()                      │
│  internal/service/vehicle/vehicle.go            │
│                                                  │
│  ① 黑名单检查                                     │
│     CheckVehicleShortlist(plate, rfid)           │
│     ├─ 在黑名单 → 拦截,返回错误                    │
│     └─ 不在 → 继续                               │
│                                                  │
│  ② 车辆查询                                      │
│     GetVehicleByPlateNumber(plate, rfid)         │
│     ├─ 找到 → 使用现有车辆                        │
│     └─ 未找到 → 新建车辆(默认 is_system类型=临时车) │
│                                                  │
│  ③ 重复入场检查                                   │
│     ┌── 已有未出场记录 → 拦截                     │
│     └── 无 → 继续                                │
│                                                  │
│  ④ 创建入场记录                                   │
│     INSERT vehicle_record                        │
│     (plate_number, rfid_tag, entry_time,         │
│      parking_lot_id, payment_status=unpaid)      │
│                                                  │
│  ⑤ 创建数字票                                    │
│     digital_ticket.Create()                      │
│     状态: pending_payment                        │
│     事件日志: [{"event":"created", ...}]          │
│                                                  │
│  ⑥ 更新车辆统计                                   │
│     vehicle.last_entry_time = now                │
│                                                  │
│  ⑦ RFID → 开闸(GateOpener)                     │
│     手动 → 仅记录,需手动命令抬杆                    │
└─────────────────────────────────────────────────┘

2.2 RFID 入场特有检查

RFID 扫描 → PassageService.HandlePassage()
  │
  ├── 防抖(3秒内重复扫描忽略)
  ├── 查询设备 → 获取通道 → 确定方向/direction
  ├── 查询通道 → allowTemporary(是否允许临停)
  ├── 查询车辆 → 判断是否为临时车(VehicleType.IsSystem)
  ├── 黑名单检查
  ├── 临时车 && 通道不允许临停 → 拦截
  └── 入场状态检查(epcStatus 防重入)

3. 出场流程

3.1 流程图

车辆到达出口
  │
  ├── 手动录入 ─────────────────────────────────┐
  │   entryExit.vue 出场Tab                      │
  │   ┌─ 输入车牌/RFID → 查询车辆               │
  │   ├─ POST /vehicle/exit/preview → 费用预览   │
  │   ├─ 显示费用 + 支付方式选择                  │
  │   ├─ 临时车: 现金收款 → 输入实收 → 计算找零   │
  │   ├─ 非临时车: 免密(payment_method=free)    │
  │   └─ POST /vehicle/exit/confirm → 确认       │
  │                                              │
  ├── RFID 自动识别 ────────────────────────────  │
  │   PassageService.handleExit()                │
  │   → 直接调 ExitConfirm(payment_method=free)  │
  │                                              │
  ▼                                              ▼
┌─────────────────────────────────────────────────────┐
│              ExitConfirm()                           │
│  internal/service/vehicle/vehicle.go                │
│                                                      │
│  ① 黑名单检查                                         │
│     CheckVehicleShortlist(plate, rfid)               │
│     └─ 在黑名单 → 拦截                                │
│                                                      │
│  ② 查询未出场记录                                      │
│     WHERE (plate=? OR rfid=?) AND exit_time IS NULL  │
│     └─ 未找到 → "车辆未入场或已出场"                     │
│                                                      │
│  ③ 计算费用                                           │
│     calculateFee(vehicle, stayTime)                  │
│     ├─ 查 fee_config(按车型)                         │
│     ├─ VIP检查(IsVip + 过期检查 + 非零值)              │
│     ├─ 免费时长内 → 0 元                               │
│     ├─ 超时计费(StartFee + units × UnitFee)          │
│     └─ 每日封顶 / VIP折扣                              │
│                                                      │
│  ④ 更新出场记录                                        │
│     vehicle_record.exit_time = now                    │
│     vehicle_record.fee = calculated                   │
│     vehicle_record.payment_method = req.method        │
│                                                      │
│  ⑤ 更新车辆统计                                        │
│     vehicle.last_exit_time / total_stay / total_fee   │
│                                                      │
│  ⑥ 创建支付记录                                        │
│     payment_record (amount, method, operator, change) │
│     vehicle_record.payment_status = "paid"            │
│                                                      │
│  ⑦ RFID → 开闸                                       │
│     手动 → 仅记录                                     │
└─────────────────────────────────────────────────────┘

3.2 计费算法(calculateFee)

calculateFee(vehicle, stayTime)
  │
  ├─ 1. 查 fee_config WHERE vehicle_type_id = ?
  │     ├─ 未找到 → 默认费率 0.1元/分钟
  │     └─ 全零配置 → 同样回退默认费率
  │
  ├─ 2. VIP 判断
  │     owner.IsVip && VipExpireTime.After(now) && !VipExpireTime.IsZero()
  │     └─ 过期 / 零值 → 非 VIP
  │
  ├─ 3. VIP 免费(isVIP && feeConfig.IsVIPFree)
  │     └─ return 0
  │
  ├─ 4. 免费时长内
  │     stayTime ≤ StartTime → return 0
  │
  ├─ 5. 超时计费
  │     extraTime = stayTime - StartTime
  │     units = ceil(extraTime / UnitTime)
  │     fee = StartFee + units × UnitFee
  │
  ├─ 6. 每日封顶
  │     fee > DailyMaxFee → fee = DailyMaxFee
  │
  └─ 7. VIP 折扣
       fee ×= VIPDiscount

4. 名单检查逻辑

CheckVehicleShortlist(plateNumber, rfidTag)
  │
  ├─ 按车牌或 RFID 查 vehicle 表
  │     └─ 未找到 → 返回 false, false
  │
  ├─ 按 vehicle_id 查 shortlist 表
  │     └─ 未找到 → 返回 false, false
  │
  └─ shortlist.ListType == "黑名单" → isBlack=true
     shortlist.ListType == "白名单" → isWhite=true

名单检查位置:
  ├─ VehicleEntry (手动+RIFD进场)      ✅
  ├─ ExitConfirm (手动+RIFD出场)       ✅
  └─ PassageService.HandlePassage      ✅

5. 数字票状态跟踪

VehicleEntry()
  │
  └─ digital_ticket.Create()
       │
       状态: pending_payment
       event_log: [{"event":"created","trigger":"manual|rfid"}]
       │
       ▼ ExitConfirm()
       │  └─ 创建 payment_record
       │
       状态: paid (手动调用)
       event_log: [...,{"event":"paid","payment_method":"cash"}]

6. 代码入口汇总

触发方式 入口函数 文件
手动进场 VehicleEntry() internal/service/vehicle/vehicle.go:177
RFID 进场 PassageService.handleEntry() internal/service/parking/passage.go:103
手动出场(预览) ExitPreview() internal/service/vehicle/vehicle.go:296
手动出场(确认) ExitConfirm() internal/service/vehicle/vehicle.go:334
RFID 出场 PassageService.handleExit() internal/service/parking/passage.go:150
计费引擎 calculateFee() internal/service/vehicle/vehicle.go:384
名单检查 CheckVehicleShortlist() internal/service/vehicle/shortlist.go:30
数字票创建 TicketService.Create() internal/modules/digital-ticket/service/service.go:52
道闸控制 GateOpener() internal/service/uhf/reader.go:88

7. 包月车进出场识别

车辆进场 / 出场
  │
  ├─ CheckVehicleShortlist(plate, rfid)
  │
  └─ shortlist.ListType == "白名单" && ExpirationTime > now
       ├─ 未过期 → 白名单放行(后续按免密处理)
       └─ 已过期 → 按临时车计费

包月车办理时自动写入 shortlist(白名单, ExpirationTime=end_date),进出场复用现有白名单检查,零改动。