Kaynağa Gözat

docs: 数字票说明文档——状态机/数据模型/API/代码结构

lq 1 ay önce
ebeveyn
işleme
5cf231ca91
1 değiştirilmiş dosya ile 180 ekleme ve 0 silme
  1. 180 0
      doc/digital-ticket.md

+ 180 - 0
doc/digital-ticket.md

@@ -0,0 +1,180 @@
+# 数字票(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;"
+```