# 数字票(Digital Ticket)说明文档 > 版本:1.0 | 日期:2026-07-23 ## 1. 概述 数字票(Digital Ticket)是停车业务中的标准化抽象模型,以电子化的"票"作为核心对象,遵循 **创建 → 支付 → 验证 → 放行** 的线性状态机模式。不绑定特定硬件或支付方式,具有高度的通用性和可扩展性。 ## 2. 核心概念 ### 设计原则 - **统一抽象**:所有进场方式(RFID、车牌识别、手动录入)最终都创建数字票 - **状态驱动**:票的状态决定下一步操作,不允许跳跃式状态变更 - **事件溯源**:关键节点记录事件日志,便于审计和异常追踪 - **二维码凭证**:以 ticket_no(UUID)作为唯一标识和验证依据 ### 与现有流程的关系 ``` 任何进场方式(手动/RFID/车牌) │ ▼ VehicleEntry() ──→ 创建 vehicle_record │ │ │ ▼ │ 创建 digital_ticket │ (状态: pending_payment) │ ▼ ExitPreview / ExitConfirm │ ▼ digital_ticket 状态 → paid → exited ``` 数字票是 `vehicle_record` 的业务状态层,不替代它。报表和监控继续用 `vehicle_record`,业务流转看 `digital_ticket` 状态。 ## 3. 状态机 ``` ┌──────────┐ ┌──→ │ expired │ ←──┐ │ │ 已过期 │ │ │ └──────────┘ │ │ 超时未支付 │ 超时未离场 │ │ ┌─────────┐ │ ┌───────────────┐ │ ┌──────┐ ┌────────┐ │ created │─→│ │pending_payment│─→│ paid │─→│ exited │ │ 已创建 │ │ 待支付 │ │ 已支付│ │ 已离场 │ └─────────┘ └───────────────┘ └──────┘ └────────┘ 车辆入场 自动转换 支付成功 验证放行 ``` | 状态 | 说明 | 触发条件 | |------|------|---------| | `created` | 车辆入场,数字票生成 | 入场事件触发 | | `pending_payment` | 待支付 | 创建完成后自动进入 | | `paid` | 已支付,费用结清 | 支付成功回调 | | `exited` | 已离场,流程结束 | 验证通过后开闸 | | `expired` | 超时未支付或未离场 | 定时任务或超时逻辑 | ### 状态转换规则 | 当前状态 | 允许转换到 | |---------|-----------| | `created` | `pending_payment` | | `pending_payment` | `paid`, `expired` | | `paid` | `exited`, `expired` | | `exited` | 无(终态) | | `expired` | 无(终态) | 状态转换是原子操作,代码层做了合法性校验,不允许跳跃式变更。 ## 4. 数据模型 ### digital_ticket 表 | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PK | 主键 | | ticket_no | TEXT(64) | UNIQUE | 票号(32位十六进制UUID) | | plate_number | TEXT(20) | | 车牌号 | | trigger_mode | TEXT(20) | | 触发方式:rfid / plate_recognition / manual | | state | TEXT(20) | default:created | 状态 | | vehicle_record_id | INTEGER | FK | 关联 vehicle_record.id | | entry_time | DATETIME | | 入场时间 | | exit_time | DATETIME | | 出场时间 | | fee | REAL | | 计费金额 | | payment_method | TEXT(20) | | 支付方式:cash / free / wechat / alipay | | payment_order_no | TEXT(64) | | 支付单号 | | paid_at | DATETIME | | 支付时间 | | expire_at | DATETIME | | 过期时间 | | event_log | TEXT | | 事件日志(JSON数组) | | created_at | DATETIME | | 创建时间 | | updated_at | DATETIME | | 更新时间 | ### event_log 格式 ```json [ {"at": "2026-07-23T15:27:08+08:00", "event": "created", "trigger": "manual"}, {"at": "2026-07-23T15:35:12+08:00", "event": "pending_payment"}, {"at": "2026-07-23T15:36:01+08:00", "event": "paid", "payment_method": "cash", "paid_amount": 20, "payment_order_no": "PO20260723001"}, {"at": "2026-07-23T15:36:05+08:00", "event": "exited"} ] ``` ## 5. API 接口 | 方法 | 路径 | 说明 | 鉴权 | |------|------|------|:--:| | GET | `/digital-ticket/list` | 分页列表(按车牌/状态筛选) | JWT | | GET | `/digital-ticket/:ticketNo` | 根据票号查询详情 | JWT | | POST | `/digital-ticket/:ticketNo/pay` | 支付(→ paid) | JWT | | POST | `/digital-ticket/:ticketNo/exit` | 离场验证(→ exited) | JWT | ### 支付请求体 ```json { "payment_method": "cash", "payment_order_no": "PO20260723001", "paid_amount": 20.0 } ``` ### 列表查询参数 | 参数 | 说明 | |------|------| | plate_number | 车牌号(模糊匹配) | | state | 状态筛选 | | page | 页码 | | page_size | 每页条数 | ## 6. 代码结构 ``` internal/modules/digital-ticket/ ├── api.go # HTTP Handler + 路由注册 ├── service/ │ └── service.go # 状态机 + 事件日志 + Create/Transition └── repository/ └── repo.go # CRUD ``` ``` internal/dao/digital_ticket.go # GORM 实体 ``` ### 关键函数 | 函数 | 说明 | |------|------| | `TicketService.Create()` | 创建数字票,生成 UUID ticket_no,写入 event_log | | `TicketService.Transition()` | 状态转换,校验合法性,追加事件日志 | | `TicketService.GetByTicketNo()` | 扫码查询(二维码凭证验证入口) | ## 7. 扩展性 数字票作为统一抽象,后续可扩展: - **线上支付**:扫码 → 跳转支付页 → 回调 `PayTicket` - **自助缴费机**:票机扫码 → 调 `/ticketNo` 查询费用 → POS 支付 → 回调 `/ticketNo/pay` - **无牌车处理**:进场出纸质二维码小票(ticket_no 打印在票上) - **定时过期**:后台任务扫描 `pending_payment` 超时票 → Transition 到 `expired` ## 8. 数据库位置 ``` Windows: %APPDATA%\smart-parking\lc_garage.db → C:\Users\<用户名>\AppData\Roaming\smart-parking\lc_garage.db ``` 查询数字票数据: ```sh sqlite3 "%APPDATA%/smart-parking/lc_garage.db" "SELECT id, ticket_no, plate_number, state FROM digital_ticket;" ```