|
|
@@ -1,541 +1,41 @@
|
|
|
-# 智慧停车 — 进出场逻辑流程
|
|
|
-
|
|
|
-> 更新日期:2026-07-29
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 0. 车辆类型体系
|
|
|
-
|
|
|
-系统通过 `vehicle_type` 表管理车辆类型,每种类型关联独立的计费配置 `fee_config`。
|
|
|
-
|
|
|
-### 0.1 车辆类型分类
|
|
|
-
|
|
|
-| 维度 | 分类 | 说明 |
|
|
|
-|------|------|------|
|
|
|
-| **按身份** | 临时车(is_system=true) | 新进场自动创建,无车主关联 |
|
|
|
-| | 固定车(is_system=false) | 预先注册,关联 `Owner`,可配置 VIP |
|
|
|
-| **按名单** | 白名单(包月车) | 在 `shortlist` 表中有记录,ListType=白名单 |
|
|
|
-| | 黑名单 | 在 `shortlist` 表中有记录,ListType=黑名单 |
|
|
|
-| | 普通车辆 | 不在任何名单中 |
|
|
|
-| **按VIP** | VIP 车辆 | Owner.IsVip=true 且 VipExpireTime 未过期 |
|
|
|
-| | 非 VIP 车辆 | 无 VIP 或已过期 |
|
|
|
-
|
|
|
-### 0.2 默认类型
|
|
|
-
|
|
|
-系统初始化时自动创建唯一系统内置类型:**临时车**(Name="临时车", IsSystem=true)。
|
|
|
-管理员可在管理后台创建更多车辆类型(如"月租车"、"内部车"、"VIP 车辆"等),每种类型可独立配置 `fee_config`。
|
|
|
-
|
|
|
-### 0.3 计费配置(fee_config)
|
|
|
-
|
|
|
-每个 `vehicle_type` 对应一条计费规则:
|
|
|
-
|
|
|
-| 字段 | 说明 |
|
|
|
-|------|------|
|
|
|
-| StartTime | 免费时长(分钟) |
|
|
|
-| StartFee | 起步价 |
|
|
|
-| UnitTime | 计费单位时间(分钟) |
|
|
|
-| UnitFee | 单位费用 |
|
|
|
-| DailyMaxFee | 每日封顶(0=不封顶) |
|
|
|
-| IsVIPFree | VIP 是否免费 |
|
|
|
-| VIPDiscount | VIP 折扣率(0.8=打8折) |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 1. 触发方式总览
|
|
|
-
|
|
|
-```
|
|
|
-车辆到达/离开
|
|
|
- │
|
|
|
- ├─ ① RFID 自动识别 → reader.go → HandlePassage() → 自动进出场(开闸)
|
|
|
- ├─ ② 手动操作前端 → entryExit.vue → 手动进场 / 出场
|
|
|
- └─ ③ 摄像头车牌识别 → (待实现 P0-5)
|
|
|
-```
|
|
|
-
|
|
|
-### 触发方式对比
|
|
|
-
|
|
|
-| 特性 | RFID 自动 | 手动录入 |
|
|
|
-|------|-----------|---------|
|
|
|
-| 用户体验 | 无需停车,自动抬杆 | 需操作员输入车牌/RFID |
|
|
|
-| 入场流程 | HandlePassage → handleEntry → VehicleEntry → 自动开闸 | 前端 API → VehicleEntry → 手动抬杆(仅记录) |
|
|
|
-| 出场流程 | HandlePassage → handleExit → ExitConfirm(free) → 自动开闸 | 前端 ExitPreview → 选择支付方式 → ExitConfirm → 手动抬杆(仅记录) |
|
|
|
-| 支付方式 | 统一走 `free`(无现金交易,账户结算) | 临时车:现金收款;固定车:免密 free |
|
|
|
-| 通道限制 | 绑定 UHF 设备 → 查 Channel → 校验方向 + 临时车权限 | 无通道级校验 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 2. 进场流程
|
|
|
-
|
|
|
-### 2.1 总体流程
|
|
|
-
|
|
|
-```
|
|
|
-车辆到达入口
|
|
|
- │
|
|
|
- ├── RFID 自动识别 ─────────────────────────────────────────────
|
|
|
- │ reader.go 协程读取 UHF 数据
|
|
|
- │ → ParseBuffer() 解析报文 + CRC校验
|
|
|
- │ → HandlePassage(PassageRequest{RFIDTag: epc, DeviceCode})
|
|
|
- │
|
|
|
- ├── 手动录入 ──────────────────────────────────────────────────
|
|
|
- │ entryExit.vue 进场Tab
|
|
|
- │ 输入 车牌/RFID → 查询车辆信息(显示类型/车主)
|
|
|
- │ 选择停车区域 → 点击"确认入场"
|
|
|
- │ → POST /vehicle/entry → VehicleEntry()
|
|
|
- │
|
|
|
- ├── (摄像头计划) ──────────────────────────────────────────────
|
|
|
- │ (待实现)
|
|
|
- │
|
|
|
- ▼
|
|
|
-┌─────────────────────────────────────────────────────────────────┐
|
|
|
-│ VehicleEntry() 核心入口 │
|
|
|
-│ internal/service/vehicle/vehicle.go:177 │
|
|
|
-│ │
|
|
|
-│ ① 黑名单检查 │
|
|
|
-│ CheckVehicleShortlist(plate, rfid) │
|
|
|
-│ ├─ 在黑名单 → 拦截,返回 "车辆在黑名单中,禁止入场" │
|
|
|
-│ └─ 不在 → 继续 │
|
|
|
-│ │
|
|
|
-│ ② 车辆查询/自动创建 │
|
|
|
-│ GetVehicleByPlateNumber(plate, rfid) │
|
|
|
-│ ├─ 找到 → 使用现有车辆信息(类型/RFID/Owner) │
|
|
|
-│ └─ 未找到 → 自动创建临时车 │
|
|
|
-│ 创建 vehicle(IsSystem=true 的类型, 无Owner) │
|
|
|
-│ │
|
|
|
-│ ③ 重复入场检查 │
|
|
|
-│ WHERE (plate=? OR rfid=?) AND exit_time IS NULL │
|
|
|
-│ ├─ 已有未出场记录 → "车辆已入场,未出场" │
|
|
|
-│ └─ 无 → 继续 │
|
|
|
-│ │
|
|
|
-│ ④ 创建入场记录 │
|
|
|
-│ INSERT vehicle_record │
|
|
|
-│ (plate_number, rfid_tag, entry_time, │
|
|
|
-│ parking_lot_id, parking_space_id, payment_status='unpaid') │
|
|
|
-│ │
|
|
|
-│ ⑤ 创建数字票 │
|
|
|
-│ digital_ticket.Create() │
|
|
|
-│ 状态: pending_payment │
|
|
|
-│ event_log: [{"event":"created","trigger":"manual|rfid"}] │
|
|
|
-│ │
|
|
|
-│ ⑥ 更新车辆统计 │
|
|
|
-│ vehicle.last_entry_time = now │
|
|
|
-│ │
|
|
|
-│ ⑦ 响应返回 │
|
|
|
-│ RFID(HandlePassage内)→ GateOpener 开闸 │
|
|
|
-│ 手动 → 仅返回成功,操作员另行手动抬杆 │
|
|
|
-└─────────────────────────────────────────────────────────────────┘
|
|
|
-```
|
|
|
-
|
|
|
-### 2.2 RFID 入场特有流程(HandlePassage)
|
|
|
-
|
|
|
-```
|
|
|
-RFID 上报 → reader.go handleReportData()
|
|
|
- │
|
|
|
- ├── 解析 EPC(hex编码 12字节)
|
|
|
- ├── 构造 PassageRequest{RFIDTag: epc, DeviceCode: 设备编码}
|
|
|
- └── HandlePassage()
|
|
|
- │
|
|
|
- ├── 1. 防抖(3秒内同一 identifier 的重复请求忽略)
|
|
|
- │ identifier = RFID优先,其次车牌
|
|
|
- │ passageLastTime[identifier] 记录时间戳
|
|
|
- │
|
|
|
- ├── 2. 查 UHF 设备 → 获取绑定通道
|
|
|
- │ device = first(uhf_reader WHERE device_code = ?)
|
|
|
- │ channel = device.Channel
|
|
|
- │
|
|
|
- ├── 3. 获取通道方向
|
|
|
- │ channel.Direction → "in" / "out" / "inout"
|
|
|
- │
|
|
|
- ├── 4. 查询车辆
|
|
|
- │ v = GetVehicleByPlateNumber(PlateNumber, RFIDTag)
|
|
|
- │ isTempVehicle = v.VehicleType.IsSystem 或 未找到车辆
|
|
|
- │
|
|
|
- ├── 5. 黑名单检查
|
|
|
- │ CheckVehicleShortlist(plate, rfid)
|
|
|
- │
|
|
|
- ├── 6. 临时车规则检查
|
|
|
- │ if isTempVehicle && !channel.AllowTemporary
|
|
|
- │ → "临时车不允许此通道" 拦截
|
|
|
- │
|
|
|
- ├── 7. 方向判断
|
|
|
- │ direction == "in" → handleEntry()
|
|
|
- │
|
|
|
- ├── 8. handleEntry() 入场状态检查
|
|
|
- │ passageStatus[identifier] → true? "车辆已在场内" 拦截
|
|
|
- │
|
|
|
- ├── 9. GateOpener(deviceCode, 2) → 开闸
|
|
|
- │ 通过 UHF 设备继电器控制道闸(Relay1 抬起)
|
|
|
- │
|
|
|
- ├── 10. VehicleEntry() → 创建入场记录
|
|
|
- │
|
|
|
- └── 11. passageStatus[identifier] = true
|
|
|
-```
|
|
|
-
|
|
|
-### 2.3 手动入场特有流程(entryExit.vue)
|
|
|
-
|
|
|
-```
|
|
|
-操作员打开「进出场操作」页面
|
|
|
- │
|
|
|
- ├── 选择「进场」Tab
|
|
|
- │
|
|
|
- ├── 输入车牌 或 RFID → 点击查询
|
|
|
- │ ├─ 查到车辆 → 显示车辆类型、车主、VIP状态
|
|
|
- │ └─ 未查到 → 显示 "未找到车辆" 提示,仍可入场(自动创建临时车)
|
|
|
- │
|
|
|
- ├── 选择停车区域(下拉框选择 parking_lot)
|
|
|
- │
|
|
|
- └── 点击「确认入场」
|
|
|
- → POST /vehicle/entry → VehicleEntry()
|
|
|
- → 成功:弹窗提示 "入场成功"
|
|
|
- → 失败:弹窗提示错误原因
|
|
|
-
|
|
|
-注意:手动入场不涉及道闸控制,操作员需另行手动抬杆。
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 3. 出场流程
|
|
|
-
|
|
|
-### 3.1 总体流程
|
|
|
-
|
|
|
-```
|
|
|
-车辆到达出口
|
|
|
- │
|
|
|
- ├── RFID 自动识别 ────────────────────────────────────────────
|
|
|
- │ HandlePassage() → direction == "out" → handleExit()
|
|
|
- │ → 直接调用 ExitConfirm(free, 免密)
|
|
|
- │ → 自动开闸
|
|
|
- │
|
|
|
- ├── 手动操作 ─────────────────────────────────────────────────
|
|
|
- │ entryExit.vue 出场Tab
|
|
|
- │ ① 输入车牌/RFID → 查询车辆
|
|
|
- │ ② 调用 ExitPreview → 显示费用/停留时间/车型/是否临时车
|
|
|
- │ ③ 选择支付方式(临时车+现金: 输入实收金额)
|
|
|
- │ ④ 点击"确认出场并支付"
|
|
|
- │ → POST /vehicle/exit/confirm → ExitConfirm()
|
|
|
- │ → 创建支付记录
|
|
|
- │
|
|
|
- ├── (摄像头计划) ─────────────────────────────────────────────
|
|
|
- │ (待实现)
|
|
|
- │
|
|
|
- ▼
|
|
|
-┌─────────────────────────────────────────────────────────────────────┐
|
|
|
-│ ExitConfirm() 核心入口 │
|
|
|
-│ internal/service/vehicle/vehicle.go:339 │
|
|
|
-│ │
|
|
|
-│ ① 黑名单检查(出场同样拦截) │
|
|
|
-│ CheckVehicleShortlist(plate, rfid) │
|
|
|
-│ └─ 在黑名单 → "车辆在黑名单中,禁止出场" │
|
|
|
-│ │
|
|
|
-│ ② 查询未出场记录 │
|
|
|
-│ WHERE (plate=? OR rfid=?) AND exit_time IS NULL │
|
|
|
-│ └─ 未找到 → "车辆未入场或已出场" │
|
|
|
-│ │
|
|
|
-│ ③ 计算费用 │
|
|
|
-│ calculateFee(vehicle, stayMinutes) │
|
|
|
-│ ├─ 查 fee_config(按 vehicle_type_id) │
|
|
|
-│ ├─ VIP检查(IsVip + 过期时间未到 + 非零值) │
|
|
|
-│ ├─ 免费时长内 → 0 元 │
|
|
|
-│ ├─ 超时计费(StartFee + units × UnitFee) │
|
|
|
-│ ├─ 每日封顶 │
|
|
|
-│ └─ VIP折扣 │
|
|
|
-│ │
|
|
|
-│ ④ 更新出场记录 │
|
|
|
-│ vehicle_record.exit_time = now │
|
|
|
-│ vehicle_record.stay_time = duration │
|
|
|
-│ vehicle_record.fee = calculated │
|
|
|
-│ vehicle_record.payment_method = req.method │
|
|
|
-│ │
|
|
|
-│ ⑤ 更新车辆统计 │
|
|
|
-│ vehicle.last_exit_time = now │
|
|
|
-│ vehicle.total_stay_time += duration │
|
|
|
-│ vehicle.total_fee += fee │
|
|
|
-│ │
|
|
|
-│ ⑥ 创建支付记录 │
|
|
|
-│ payment_record (record_id, method, amount, paid_amount, │
|
|
|
-│ change_amount, operator_id, paid_at) │
|
|
|
-│ vehicle_record.payment_status = "paid" │
|
|
|
-│ │
|
|
|
-│ ⑦ 响应返回 │
|
|
|
-│ RFID(handleExit内)→ GateOpener 开闸 │
|
|
|
-│ 手动 → 仅返回成功 │
|
|
|
-└─────────────────────────────────────────────────────────────────────┘
|
|
|
-```
|
|
|
-
|
|
|
-### 3.2 RFID 出场特有流程(handleExit)
|
|
|
-
|
|
|
-```
|
|
|
-RFID 上报 → HandlePassage() → direction == "out"
|
|
|
- │
|
|
|
- ├── 1. 防抖检查(与入场共用逻辑)
|
|
|
- ├── 2. 查设备/通道(同上)
|
|
|
- ├── 3. 判断方向为 out
|
|
|
- │
|
|
|
- └── handleExit()
|
|
|
- │
|
|
|
- ├── 打印日志 + GateOpener 开闸(先开闸,后处理出场)
|
|
|
- │
|
|
|
- ├── 构造 ExitConfirmRequest
|
|
|
- │ PaymentMethod: "free" ← 重要!RFID 出场免密
|
|
|
- │ PaidAmount: 0
|
|
|
- │ OperatorID: 0(系统自动)
|
|
|
- │
|
|
|
- ├── ExitConfirm() → 计算费用 + 记录出场 + 创建支付记录
|
|
|
- │
|
|
|
- └── passageStatus[identifier] = false(标记离场)
|
|
|
-```
|
|
|
-
|
|
|
-### 3.3 手动出场特有流程(entryExit.vue)
|
|
|
-
|
|
|
-```
|
|
|
-操作员选择「出场」Tab
|
|
|
- │
|
|
|
- ├── ① 输入车牌/RFID → 查询车辆信息
|
|
|
- │
|
|
|
- ├── ② 自动调用 ExitPreview API
|
|
|
- │ 返回:plate_number, entry_time, stay_time, fee,
|
|
|
- │ vehicle_type, is_temp(是否临时车)
|
|
|
- │
|
|
|
- ├── ③ 显示费用预览面板
|
|
|
- │ ├─ 入场时间、停留时长、应收费用
|
|
|
- │ ├─ 车辆类型(临时车/固定车)
|
|
|
- │ │
|
|
|
- │ └─ 临时车 → 选支付方式
|
|
|
- │ ├─ 现金(已实现)
|
|
|
- │ ├─ 微信(disabled,待对接)
|
|
|
- │ └─ POS机(disabled,待对接)
|
|
|
- │
|
|
|
- ├── ④ 临时车 + 现金
|
|
|
- │ └─ 输入实收金额 → 自动计算找零
|
|
|
- │
|
|
|
- ├── ⑤ 确认出场
|
|
|
- │ ├─ 临时车:校验 paid_amount >= fee
|
|
|
- │ ├─ 固定车:payment_method = "free", paid_amount = 0
|
|
|
- │ └─ 弹出确认弹窗
|
|
|
- │
|
|
|
- └── ⑥ POST /vehicle/exit/confirm → ExitConfirm()
|
|
|
- → 创建支付记录 + 更新出场
|
|
|
- → 弹窗显示:入场时间 / 停留时长 / 缴费金额
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 4. 计费算法多场景推演
|
|
|
-
|
|
|
-```
|
|
|
-calculateFee(vehicle, stayMinutes)
|
|
|
- │
|
|
|
- ├── 0. 查 fee_config WHERE vehicle_type_id = ?
|
|
|
- │ ├─ 未找到 → 默认费率 0.1元/分钟(回退)
|
|
|
- │ └─ 找到且全零字段 → 同样回退默认费率
|
|
|
- │
|
|
|
- ├── 1. VIP 判断
|
|
|
- │ vehicle.OwnerId != nil
|
|
|
- │ → 查 Owner.IsVip && VipExpireTime.After(now) && !VipExpireTime.IsZero()
|
|
|
- │
|
|
|
- ├── 2. VIP 免费
|
|
|
- │ isVIP && feeConfig.IsVIPFree → return 0
|
|
|
- │
|
|
|
- ├── 3. 免费时长内
|
|
|
- │ stayMinutes <= StartTime → return 0
|
|
|
- │
|
|
|
- ├── 4. 超时计费
|
|
|
- │ extraTime = stayMinutes - StartTime
|
|
|
- │ units = ceil(extraTime / UnitTime)
|
|
|
- │ fee = StartFee + units × UnitFee
|
|
|
- │
|
|
|
- ├── 5. 每日封顶
|
|
|
- │ fee > DailyMaxFee && DailyMaxFee > 0 → fee = DailyMaxFee
|
|
|
- │
|
|
|
- └── 6. VIP折扣
|
|
|
- isVIP && !IsVIPFree → fee *= VIPDiscount
|
|
|
-```
|
|
|
-
|
|
|
-### 场景示例
|
|
|
-
|
|
|
-| 场景 | 时长(分钟) | StartTime | StartFee | UnitTime | UnitFee | DailyMax | VIP | 结果 |
|
|
|
-|------|-----------|-----------|----------|----------|---------|----------|-----|------|
|
|
|
-| 临时车30分钟内免费 | 25 | 30 | 5 | 15 | 2 | 30 | 否 | 0 |
|
|
|
-| 临时车超1h | 90 | 30 | 5 | 15 | 2 | 30 | 否 | 5+ceil(60/15)×2=13 |
|
|
|
-| 临时车超1h封顶 | 480 | 30 | 5 | 15 | 2 | 30 | 否 | min(5+ceil(450/15)×2=65, 30)=30 |
|
|
|
-| VIP免费 | 120 | 30 | 5 | 15 | 2 | 30 | 是(IsVIPFree) | 0 |
|
|
|
-| VIP折扣 | 120 | 30 | 5 | 15 | 2 | 30 | 是(折扣0.8) | ceil(5+ceil(90/15)×2)×0.8=10.4 |
|
|
|
-| 无配置回退 | 60 | - | - | - | - | - | - | 60×0.1=6 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 5. 各车辆类型的完整进出场行为
|
|
|
-
|
|
|
-### 5.1 临时车(无固定车主)
|
|
|
-
|
|
|
-| 阶段 | 行为 |
|
|
|
-|------|------|
|
|
|
-| **入场** | • 手动:前端查询不到车辆 → 仍可入场,自动创建临时车记录 |
|
|
|
-| | • RFID:通道 AllowTemporary=true 才放行,否则拦截 |
|
|
|
-| **出场** | • 手动:费用预览 → 收款(现金)→ 确认出场 |
|
|
|
-| | • RFID:自动出场,免密 |
|
|
|
-| **计费** | 按车型 fee_config 计算,VIP 相关规则不适用(无 Owner) |
|
|
|
-
|
|
|
-### 5.2 固定车(有车主,非 VIP)
|
|
|
-
|
|
|
-| 阶段 | 行为 |
|
|
|
-|------|------|
|
|
|
-| **入场** | • 手动:查询到车辆信息(显示类型/车主)→ 确认入场 |
|
|
|
-| | • RFID:白名单校验 → 黑名单校验 → 入场 |
|
|
|
-| **出场** | • 手动:ExitPreview 后按 free(免密)出场 |
|
|
|
-| | • RFID:自动出场,免密 |
|
|
|
-| **计费** | 按 fee_config 正常计费(如有 StartFee/UnitFee 则收费) |
|
|
|
-
|
|
|
-### 5.3 VIP 车辆(车主 IsVip=true 且未过期)
|
|
|
-
|
|
|
-| 阶段 | 行为 |
|
|
|
-|------|------|
|
|
|
-| **入场** | 同固定车 |
|
|
|
-| **出场** | 同固定车 |
|
|
|
-| **计费** | • feeConfig.IsVIPFree → 免费 0 元 |
|
|
|
-| | • 否则 fee × VIPDiscount(如 0.8=8折) |
|
|
|
-
|
|
|
-### 5.4 白名单车辆(包月车)
|
|
|
-
|
|
|
-| 阶段 | 行为 |
|
|
|
-|------|------|
|
|
|
-| **定义** | 车辆加入 `shortlist` 表,ListType="白名单",记录 ExpirationTime |
|
|
|
-| **入场** | • CheckVehicleShortlist → isWhite=true → 放行(无拦截) |
|
|
|
-| | • 即使 ExpirationTime 过期,白名单不拦截入场 |
|
|
|
-| **出场** | • 不拦截出场 |
|
|
|
-| | • 包月车结费规则在 Shortlist 过期处理中决定:若 ExpirationTime < now 则按临时车计费;若未过期则依赖 fee_config 中的费用判断 |
|
|
|
-
|
|
|
-**说明**:目前代码中白名单本身**不修改计费逻辑**,只是放行/不拦截。
|
|
|
-包月车真正的费用减免靠 `fee_config` 的 StartTime/IsVIPFree 等配置控制,
|
|
|
-或通过 `MonthlyCard` 模块进行包月办理管理(只记录办理记录)。
|
|
|
-
|
|
|
-### 5.5 黑名单车辆
|
|
|
-
|
|
|
-| 阶段 | 行为 |
|
|
|
-|------|------|
|
|
|
-| **入场** | CheckVehicleShortlist → isBlack=true → **拦截**,禁止入场 |
|
|
|
-| **出场** | ExitConfirm → CheckVehicleShortlist → isBlack=true → **拦截** |
|
|
|
-| | 注意:黑名单在出场也拦截,需要管理员手动解除黑名单才能出场 |
|
|
|
-| **设置** | 通过后台「黑白名单」管理页面将车辆加入黑名单 |
|
|
|
-
|
|
|
-### 5.6 进出场状态检查矩阵
|
|
|
-
|
|
|
-| 状态 | 手动入场 | RFID入场 | 手动出场 | RFID出场 |
|
|
|
-|------|---------|---------|---------|---------|
|
|
|
-| 黑名单 | ❌ 拦截 | ❌ 拦截 | ❌ 拦截 | ❌ 拦截 |
|
|
|
-| 未入场 | ✅ 正常 | ✅ 正常 | ❌ "未入场或已出场" | ❌(先开闸再出错?待商榷) |
|
|
|
-| 已在场内 | ❌ "已入场,未出场" | ❌ "已在场内" | ✅ 正常 | ✅ 正常 |
|
|
|
-| 临时车+不允许临时通道 | N/A(手动无通道校验) | ❌ 拦截 | N/A | N/A |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 6. 数据表变更汇总
|
|
|
-
|
|
|
-### 6.1 入场写入
|
|
|
-
|
|
|
-| 表 | 操作 | 关键字段 |
|
|
|
-|----|------|---------|
|
|
|
-| vehicle | 查/创建 | plate_number, rfid_tag, vehicle_type_id, last_entry_time |
|
|
|
-| vehicle_record | INSERT | plate_number, rfid_tag, entry_time=now, parking_lot_id, payment_status=unpaid |
|
|
|
-| digital_ticket | INSERT | ticket_no, plate_number, trigger_mode, state=pending_payment, entry_time=now |
|
|
|
-
|
|
|
-### 6.2 出场写入
|
|
|
-
|
|
|
-| 表 | 操作 | 关键字段 |
|
|
|
-|----|------|---------|
|
|
|
-| vehicle_record | UPDATE | exit_time=now, stay_time=fee, payment_method, payment_status=paid |
|
|
|
-| vehicle | UPDATE | last_exit_time, total_stay_time+=, total_fee+= |
|
|
|
-| payment_record | INSERT | record_id, payment_method, amount, paid_amount, change_amount, operator_id, paid_at=now |
|
|
|
-| digital_ticket | UPDATE | state→paid/exit(手动调用Transition) |
|
|
|
-
|
|
|
-### 6.3 数字票状态转换
|
|
|
-
|
|
|
-```
|
|
|
-Created → PendingPayment → Paid → Exited
|
|
|
- ↘ ↘
|
|
|
- Expired Expired
|
|
|
-```
|
|
|
-
|
|
|
-创建时直接到 PendingPayment,出场支付后到 Paid,可再转为 Exited。
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 7. 边界情况 & 注意事项
|
|
|
-
|
|
|
-### 7.1 RFID 防抖设计
|
|
|
-
|
|
|
-```go
|
|
|
-passageDebounceSec = 3 // 3 秒内同一 RFID/车牌忽略重复请求
|
|
|
-```
|
|
|
-同一设备读头短时间内可能多次读取同一标签,防抖避免重复入场/出场。
|
|
|
-
|
|
|
-### 7.2 出场先开闸后扣费
|
|
|
-
|
|
|
-```go
|
|
|
-// passage.go handleExit() — 先开闸
|
|
|
-GateOpener(req.DeviceCode, 2)
|
|
|
-// 再处理出场扣费
|
|
|
-vehicleSvc.ExitConfirm(confirmReq, 0)
|
|
|
-```
|
|
|
-**风险**:如果 ExitConfirm 失败(如数据库错误),车辆已出场但未记录。
|
|
|
-设计意图是保证通行效率 > 计费准确性,损失可通过后台对账发现。
|
|
|
-
|
|
|
-### 7.3 RFID 出场免密
|
|
|
-
|
|
|
-RFID 自动出场统一走 `PaymentMethod: "free"`,即设备触发不开单、不收费、仅记录。
|
|
|
-临时车通过 RFID 出场时一样免密——这意味着 RFID 出场不产生实收费用。
|
|
|
-
|
|
|
-**设计决策**:如果需要临时车 RFID 出场时收费,需要在出口部署收费岗亭或对接线上支付。目前设计为:
|
|
|
-- **RFID 通道**:封闭场景(如小区/园区),车辆都经过授权(白名单或固定车),计费走账户/月租
|
|
|
-- **临时车**:走人工通道,现金/扫码支付后出场
|
|
|
-
|
|
|
-### 7.4 手动出场支付方法
|
|
|
-
|
|
|
-| 场景 | payment_method | paid_amount | operator_id |
|
|
|
-|------|---------------|-------------|-------------|
|
|
|
-| 临时车现金收款 | "cash" | 实收金额 | 当前登录用户ID |
|
|
|
-| 固定车免密 | "free" | 0 | 当前登录用户ID |
|
|
|
-| RFID自动出场 | "free" | 0 | 0(系统) |
|
|
|
-
|
|
|
-### 7.5 黑名单出场拦截
|
|
|
-
|
|
|
-黑名单在 `ExitConfirm()` 中同样检查。如果车辆在出场时被加入黑名单,会拦截出场:
|
|
|
-需要先通过「黑白名单管理」移除该车辆的黑名单状态,才能正常出场。
|
|
|
-
|
|
|
-### 7.6 小区/园区封闭场景 vs 公共停车场
|
|
|
-
|
|
|
-| 场景 | RFID 通道 | 手动通道 |
|
|
|
-|------|----------|---------|
|
|
|
-| 小区/园区(封闭) | 主要出入口,预注册车辆自动放行 | 访客登记入场 |
|
|
|
-| 公共停车场 | 未实现 | 临时车收费出场 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 8. 相关 API 汇总
|
|
|
-
|
|
|
-| 方法 | 路由 | 功能 | 文件 |
|
|
|
-|------|------|------|------|
|
|
|
-| POST | /vehicle/entry | 手动入场 | `internal/api/v1/vehicle/vehicle.go:131` |
|
|
|
-| POST | /vehicle/exit | 出场(旧接口) | `internal/api/v1/vehicle/vehicle.go:148` |
|
|
|
-| POST | /vehicle/exit/preview | 出场费用预览 | `internal/modules/payment/api.go:17` |
|
|
|
-| POST | /vehicle/exit/confirm | 确认出场+支付 | `internal/modules/payment/api.go:32` |
|
|
|
-| GET | /vehicle/get-by-plate | 查询车辆信息 | `internal/api/v1/vehicle/vehicle.go:66` |
|
|
|
-| POST | /vehicle/entry/rfid | RFID 自动进出场 | `internal/service/uhf/reader.go` 内部调用 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 9. 代码入口汇总
|
|
|
-
|
|
|
-| 触发方式 | 入口函数 | 文件 |
|
|
|
-|---------|---------|------|
|
|
|
-| 手动进场 | `VehicleEntry()` | `internal/service/vehicle/vehicle.go:177` |
|
|
|
-| RFID 进场 | `PassageService.handleEntry()` | `internal/service/parking/passage.go:103` |
|
|
|
-| 手动出场(预览) | `ExitPreview()` | `internal/service/vehicle/vehicle.go:302` |
|
|
|
-| 手动出场(确认) | `ExitConfirm()` | `internal/service/vehicle/vehicle.go:339` |
|
|
|
-| RFID 出场 | `PassageService.handleExit()` | `internal/service/parking/passage.go:148` |
|
|
|
-| RFID 统一入口 | `HandlePassage()` | `internal/service/parking/passage.go:40` |
|
|
|
-| 计费引擎 | `calculateFee()` | `internal/service/vehicle/vehicle.go:390` |
|
|
|
-| 名单检查 | `CheckVehicleShortlist()` | `internal/service/vehicle/shortlist.go:30` |
|
|
|
-| 数字票创建 | `TicketService.Create()` | `internal/modules/digital-ticket/service/service.go:46` |
|
|
|
-| 道闸控制 | `GateOpener()` / `OpenGateByDeviceCode()` | `internal/service/uhf/reader.go:96` |
|
|
|
-| UHF 数据解析 | `parseReportData()` | `internal/service/uhf/reader.go:221` |
|
|
|
-| UHF 上报处理 | `handleReportData()` | `internal/service/uhf/reader.go:250` |
|
|
|
-| 支付处理 | `ProcessPayment()` | `internal/modules/payment/service/service.go:29` |
|
|
|
+# 进出场逻辑流程图
|
|
|
+
|
|
|
+> 更新日期:2026-08-07
|
|
|
+> 本文档为流程总览;字段和异常细节见 [进出场流程详解](进出场流程详解.md)。
|
|
|
+
|
|
|
+## 1. 统一流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ A[设备事件或人工录入] --> B[校验车辆标识]
|
|
|
+ B --> C[通过设备编码定位设备和通道]
|
|
|
+ C --> D{通道方向}
|
|
|
+ D -->|入场| E[定位未关闭停车会话]
|
|
|
+ E -->|不存在| F[创建停车会话和数字票]
|
|
|
+ E -->|已存在| G[拒绝重复入场]
|
|
|
+ F --> H[保存入口通道和设备快照]
|
|
|
+ H --> I[请求开闸]
|
|
|
+ D -->|出场| J[定位停车会话和数字票]
|
|
|
+ J --> K[预览停车费用]
|
|
|
+ K --> L{数字票是否已支付}
|
|
|
+ L -->|否| M[按入口和方式完成支付]
|
|
|
+ M --> N[写支付流水并更新会话和数字票]
|
|
|
+ L -->|是| O[复用既有支付结果]
|
|
|
+ N --> P[保存出口通道和设备快照]
|
|
|
+ O --> P
|
|
|
+ P --> Q[关闭会话并标记数字票已离场]
|
|
|
+ Q --> R[请求开闸]
|
|
|
+```
|
|
|
+
|
|
|
+## 2. 状态约束
|
|
|
+
|
|
|
+| 操作 | 前置状态 | 成功后的状态 |
|
|
|
+| --- | --- | --- |
|
|
|
+| 入场 | 不存在未关闭会话 | 会话在场,数字票 `pending_payment` |
|
|
|
+| 支付 | 会话在场且未支付 | 会话已支付,数字票 `paid` |
|
|
|
+| 出场 | 会话在场且已支付,或合法零费用放行 | 会话关闭,数字票 `exited` |
|
|
|
+| 再次支付/出场 | 已支付或已关闭 | 拒绝,不产生新流水 |
|
|
|
+
|
|
|
+## 3. 设备失败原则
|
|
|
+
|
|
|
+业务数据事务成功后才请求开闸。开闸失败不回滚已完成的支付或出场,而是返回设备失败状态,由操作员重试开闸或按异常流程处理。
|