Files
hfb_sys/docs/order-testing-messaging-analysis.md
T

20 KiB
Raw Blame History

HFB Sys 订单购买测试 & 消息系统增强 —— 综合分析报告

生成日期:2026-05-24 状态:订单支付与开发充值主链路已实施;消息系统增强留待后续

说明:本文来自改造前分析,用于保留问题背景和测试思路。文中对“当前链路”的描述对应改造前状态;当前分支已接入 pending_payment、支付冻结、开发充值、结账确认等订单系统能力,消息系统增强仍未纳入本轮改造。


第一部分:如何进行实际的订单购买测试

1. 当前完整订单购买链路

从前端到后端,当前链路如下:

租客浏览商品列表 → 点击商品详情 → 点击"立即租赁"
    ↓
MobileListingDetailView.vue 调用 createOrder(listingId)
    ↓
POST /api/orders {listing_id}
    ↓
handler.CreateOrder() → 验证 listing 状态(published & approved)
    ↓
service.CreateOrder(ctx, userID, listingID)
    ↓
repository.Create(tx) → 一条大事务:
    ├── SELECT listing + account FOR UPDATE (悲观锁防并发)
    ├── 校验: listing.status=published, account.status=published
    ├── INSERT rental_orders (status=pending_handoff, rent_hours=24 硬编码)
    ├── UPDATE listing.status → rented, account.status → rented
    ├── INSERT account_snapshot (JSON 字段, 记录下单时资产快照)
    ├── INSERT wallet_ledger (order_lock, 模拟冻结 rent_amount + deposit_amount)
    ├── UPDATE wallet_accounts (frozen_balance ↑)
    ├── notification.Append() → 通知号主+租客
    └── COMMIT

关键发现——无支付环节POST /api/orders 调用后直接进入 pending_handoff,完全没有支付确认步骤。这是因为项目计划明确说"一期先做账务模型,不直接接真实支付和提现"。

2. 当前存在的 9 个关键缺失

# 缺失项 影响 涉及文件
1 无支付确认步骤 订单创建即生效,无资金冻结验证 order/repository.go Create()
2 租期硬编码 24h internalOrderHours = 24,用户无法选择 order/repository.go:21
3 无余额校验 下单时未检查租客 available_balance >= rent_amount + deposit_amount order/repository.go Create()
4 前端余额硬编码 MobileProfileView.vue 写死 ¥0.00,未调 API MobileProfileView.vue
5 钱包首次查询才创建 GET /api/wallet/balance 首次访问才初始化账户 wallet/repository.go
6 无充值入口 没有任何充值接口或页面 全项目
7 站内信前端空壳 MobileMessagesView.vue 只显示 van-empty MobileMessagesView.vue
8 无租期即将结束提醒 项目计划中规划了但未实现 超时扫描任务
9 短信完全未接 只有登录验证码的 mock,业务通知短信完全缺失 integrations/sms/

3. 要完成端到端测试必须补充的最小功能集

必须在测试前补充的功能(按依赖顺序):

第1步:余额充值(测试前置条件)
    └── POST /api/wallet/recharge {amount} → 给测试用户加余额

第2步:租期选择(核心流程)
    └── 前端:下单时增加租期选择组件
    └── 后端:CreateOrderDTO 增加 rent_hours 字段

第3步:支付确认步骤(状态机改造)
    └── 新增订单状态:pending_payment
    └── POST /api/orders → pending_payment(而非直接 pending_handoff
    └── POST /api/orders/{id}/pay → 校验余额 → 扣款 → pending_handoff

第4步:前端站内信接入
    └── MobileMessagesView.vue 接入 fetchNotifications API
    └── 底部导航栏增加未读消息角标

4. 支付确认步骤的推荐设计

建议的状态机改造(最小侵入):

POST /api/orders
    ↓
[新增] pending_payment(待支付,默认 15 分钟超时自动取消)
    ↓
POST /api/orders/{id}/pay
    ├── 校验:订单归属租客、status=pending_payment、未超时
    ├── 校验:wallet_accounts.available_balance >= rent_amount + deposit_amount
    ├── UPDATE wallet_accounts: available_balance -= total, frozen_balance += total
    ├── INSERT wallet_ledger: order_lock (冻结流水)
    ├── UPDATE rental_orders: status → pending_handoff
    ├── UPDATE listing + account: status → rented
    └── notification.Append() → 通知双方

对应的新 API

  • POST /api/orders/{id}/pay — 确认支付
  • GET /api/orders/{id}/payment-status — 查询支付状态
  • 超时扫描任务新增:pending_payment 超过 15 分钟 → 自动取消

5. 完整测试场景清单

5.1 正常流程测试

场景 步骤 验证点
完整租赁闭环 浏览→下单→支付→号主交接→收号确认→租赁→结账→号主确认→完成 所有状态流转正确,资金流水完整
租期选择 选择不同租期(1h/3h/12h/24h) rent_end_at 正确计算,租金按比例
多商品下单 不同商品分别下单 互不影响,各自锁定
结账修改流程 号主修改结账金额→租客同意修正 状态流转:pending_checkout_confirm → pending_checkout_accept → completed

5.2 边界与异常测试

场景 验证点
余额不足下单 返回明确错误,不创建订单
已租出商品下单 SELECT FOR UPDATE 后检测到 status≠published,拒绝
审核未通过商品下单 拒绝(review_status≠approved
自己租自己 拒绝(owner_id = renter_id
未实名租客下单 根据 system_config 的 realname_required_for_order 配置
非法状态转换 已取消订单不能再次取消,已完成订单不能结账等
结账争议流程 任一方发起争议→客服仲裁→完成/关闭

5.3 并发测试

场景 验证点
2 人同时下单同一商品 SELECT FOR UPDATE 保证只有一人成功
同时支付+超时取消 互斥,不会出现既支付又取消
并发结算 wallet_ledger 不出现负数或重复入账
并发创建订单 库存锁定正确

5.4 超时测试(依赖超时扫描任务)

超时类型 默认阈值 期望行为
待支付超时 15 min 订单自动取消
号主待交接超时 30 min handoff_status → owner_timeout,通知双方
租客确认收号超时 30 min status → abnormal,客服介入
租客逾期未结账 10 min 宽限 status → overdue
号主确认结账超时 120 min status → abnormal

5.5 安全测试

场景 验证点
未登录访问 所有写接口返回 401
跨用户操作 A 不能取消 B 的订单,不能确认 B 的收号
后台接口保护 普通用户 token 不能访问 /api/admin/*
重复提交 防重机制(前端 loading + 后端幂等)
SQL 注入 所有查询通过 GORM 参数化
越权查看 用户不能查看他人订单、钱包、交接记录

6. 具体测试实施方案

6.1 测试环境搭建

# 1. 启动基础设施
docker compose -f deploy/docker-compose.dev.yml up -d

# 2. 初始化数据库
docker exec -i hfb-mysql mysql -uhfb -psecret hfb_sys < backend/migrations/000001_init.sql

# 3. 启动后端
cd backend && cp .env.example .env && go run ./cmd/api

# 4. 启动前端
cd frontend && npm install && npm run dev

6.2 手动端到端测试步骤(核心路径)

前置条件:准备两个测试手机号(号主A + 租客B)

Step 1: 号主A 注册/登录
  POST /api/auth/sms/send {phone: "138xxxx0001"}
  POST /api/auth/sms/login {phone: "138xxxx0001", code: "从日志获取"}

Step 2: 号主A 实名认证(mock
  POST /api/realname/start {name: "张三", id_no: "110101199001011234"}

Step 3: 号主A 发布商品
  POST /api/listings {
    game_name: "三角洲行动", 
    server_region: "微信区",
    haf_coin_amount: 5000000,
    price_hourly: 10,
    deposit_amount: 100,
    min_rent_hours: 1,
    max_rent_hours: 24
  }
  POST /api/listings/{id}/submit-review

Step 4: 后台审核通过
  POST /api/admin/auth/login {username: "admin", password: "admin123456", captcha: "..."}
  POST /api/admin/listings/{id}/approve

Step 5: 租客B 注册/登录
  (同 Step 1,使用 138xxxx0002)

Step 6: 租客B 充值(当前需手动)
  直接改数据库或通过调试接口给租客B加余额

Step 7: 租客B 浏览并下单
  GET /api/listings → 查看商品
  POST /api/orders {listing_id, rent_hours: 3}

Step 8: 租客B 支付(当前直接跳到 pending_handoff,后续需补充)
  验证订单状态

Step 9: 号主A 提交交接说明
  POST /api/orders/{id}/handoff {content: "登录方式:..."}

Step 10: 租客B 确认收号
  POST /api/orders/{id}/confirm-receive

Step 11: 租客B 发起结账
  POST /api/orders/{id}/checkout {
    content: "使用结束",
    consumable_amount: 30,
    coin_consumed_m: 0.5
  }

Step 12: 号主A 确认结账
  POST /api/orders/{id}/checkout/confirm

Step 13: 验证结果
  GET /api/orders/{id} → status=completed
  GET /api/wallet/ledger → 流水完整

6.3 自动化测试建议

层级 工具 覆盖内容
单元测试 Go testing + testify Repository 的事务方法,Service 参数校验
集成测试 Go testing + testcontainers 真实 MySQL/Redis 下的完整流程
API 测试 Postman/Newman 或 Go httptest 所有 API 端点的正常+异常路径
E2E 测试 Playwright 或 Cypress 前端页面操作完整流程
并发测试 Go benchmark + goroutines 并发下单、并发结算

第二部分:应增加什么消息系统

1. 现有消息系统现状评估

1.1 站内信体系

好消息:后端站内信的写入非常完整——所有 18 个关键业务节点都在事务内通过 notification.Append(tx, ...) 写入,覆盖了订单全生命周期的每个状态变更。

文件 notification.Append 调用次数 场景
order/repository.go 9 次 创建/取消/交接/收号/结账/修改结账/客服关闭/标记异常/完成结算
dispute/repository.go 4 次 发起申诉(2)/仲裁完成(2)
ordertimeout/job.go 4 次 号主超时/收号超时/逾期/确认结账超时

坏消息:前端完全没接——MobileMessagesView.vue 只有一个 van-empty 组件,Desktop NotificationsView.vue 虽然接入了 API 但缺乏未读数角标和分类。

1.2 表结构分析

notifications (
  id, user_id, type, title, content,
  biz_type, biz_id, read_at, created_at
)

当前优点

  • 简洁高效
  • (user_id, read_at, created_at) 联合索引支持快速分页查询

当前不足

  • 缺少 category 字段(订单消息/系统通知/公告无法区分)
  • 缺少软删除标记
  • 缺少 extra_data (JSON) 用于存储跳转链接等扩展信息

1.3 对照项目计划的缺口

项目计划第 8 章规划的但尚未实现的场景:

规划场景 当前状态 优先级
商品审核通过/拒绝通知号主 已实现
租期即将结束提醒 未实现 🔴
账户冻结/解冻通知用户 未实现 🟡
短信登录验证码 mock
租期即将到期短信 未实现 🔴
申诉被受理短信 未实现 🟡
仲裁结果短信 未实现 🟡
WebSocket/SSE 实时推送 未实现 🟢

2. 推荐的消息系统增强架构

2.1 站内信增强(Phase 1 — 立即做)

最小可行方案:
┌─────────────────────────────────────────────────┐
│ notifications 表扩展                              │
├─────────────────────────────────────────────────┤
│ + category VARCHAR(32)  -- order|system|announce │
│ + link_type VARCHAR(32)  -- order_detail|...     │
│ + link_id   BIGINT                               │
│ + extra_data JSON       -- 扩展数据              │
├─────────────────────────────────────────────────┤
│ 新增 API:                                       │
│ GET /api/notifications/unread-count              │
│ POST /api/notifications/batch-read               │
└─────────────────────────────────────────────────┘

前端改动:
├── MobileMessagesView.vue:接入 fetchNotifications,消息列表
├── 底部导航栏:未读消息红点角标
├── Desktop NotificationsView.vue:增强消息分类和已读标记
└── Router:增加消息未读数 store

2.2 短信系统增强(Phase 2 — 短期)

设计短信 Provider 抽象层:

internal/integrations/sms/
├── provider.go      ← Provider 接口
│   type Provider interface {
│       Send(ctx, phone, templateCode string, params map[string]string) error
│       SendBatch(ctx, phones []string, ...) error
│   }
├── mock.go          ← MockProvider(开发环境)
├── aliyun.go        ← AliyunProvider(生产环境)
├── limiter.go       ← 限流器(基于 Redis)
│   - 手机号维度:60s/次,10次/天
│   - IP 维度:100次/小时
│   - 场景维度:可配置
└── logger.go        ← 发送日志(sms_logs 表)

短信触发场景:
├── 登录验证码(已有 mock)
├── 租期即将到期提醒(新增 🔴)
├── 订单申诉被受理(新增 🟡)
├── 仲裁结果通知(新增 🟡)
└── 高风险安全提醒(预留)

2.3 SSE 实时推送架构(Phase 3 — 中期)

推荐 SSEServer-Sent Events)而非 WebSocket:
理由:本场景是单向推送(服务端→客户端),SSE 更轻量,
     基于 HTTP,不需要额外协议升级,断线自动重连。

Go 侧架构:
┌──────────────────────────────────────┐
│ SSEBroker (单例)                      │
├──────────────────────────────────────┤
│ clients map[userID]*SSEClient        │
│ Notify(userID, event)                │
│ Broadcast(event)                     │
└──────────────────────────────────────┘
         ↓ 事件分发
┌──────────────────────────────────────┐
│ SSE Events:                          │
│ - order.status_changed               │
│ - order.handoff_updated              │
│ - order.checkout_updated             │
│ - notification.new                   │
│ - dispute.updated                    │
└──────────────────────────────────────┘

Vue 侧:
// composables/useSSE.ts
const eventSource = new EventSource('/api/sse/stream?token=xxx')
eventSource.addEventListener('notification.new', (e) => {
  const data = JSON.parse(e.data)
  notificationStore.incrementUnread()
})

重连策略:
- EventSource 原生支持自动重连
- 可设置 retry interval: 3000ms
- 重连时重新验证 token

2.4 订单全链路状态变更推送矩阵

订单状态变更 → 推送内容设计:

pending_payment → 租客: {type:"order.created", title:"订单已创建", orderId, amount, expireAt}
                  号主: {type:"order.new_rental", title:"有新的租用请求"}

pending_handoff → 租客: {type:"order.paid", title:"支付成功,等待号主交接"}
                  号主: {type:"order.pending_handoff", title:"请尽快完成账号交接"}

pending_renter_confirm → 租客: {type:"handoff.submitted", title:"号主已完成交接,请确认收号"}

renting → 号主: {type:"handoff.confirmed", title:"租客已确认收号,租赁开始"}

[租期结束前5分钟] → 租客: {type:"rental.expiring", title:"租期即将结束"}

pending_checkout_confirm → 号主: {type:"checkout.submitted", title:"租客已发起结账"}

completed → 双方: {type:"order.completed", title:"订单已完成"}

3. 数据库设计建议

3.1 新增表

-- 短信发送日志
CREATE TABLE sms_logs (
  id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  phone VARCHAR(20) NOT NULL,
  template_code VARCHAR(64) NOT NULL,
  template_params JSON,
  status VARCHAR(16) NOT NULL DEFAULT 'pending',  -- pending|sent|failed
  provider VARCHAR(32) NOT NULL,
  provider_msg_id VARCHAR(128),
  error_msg VARCHAR(512),
  biz_type VARCHAR(32),
  biz_id BIGINT UNSIGNED,
  user_id BIGINT UNSIGNED,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_sms_logs_phone_created (phone, created_at),
  INDEX idx_sms_logs_status (status)
);

-- SSE 推送记录(可选,用于对账)
CREATE TABLE push_records (
  id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT UNSIGNED NOT NULL,
  event_type VARCHAR(64) NOT NULL,
  payload JSON,
  delivered TINYINT DEFAULT 0,
  delivered_at DATETIME,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_push_records_user (user_id, created_at)
);

3.2 notifications 表扩展

ALTER TABLE notifications
  ADD COLUMN category VARCHAR(32) NOT NULL DEFAULT 'order' 
    COMMENT 'order|system|announce|security',
  ADD COLUMN link_type VARCHAR(32) DEFAULT NULL 
    COMMENT 'order_detail|listing_detail|dispute_detail|wallet|url',
  ADD COLUMN link_id BIGINT UNSIGNED DEFAULT NULL,
  ADD COLUMN extra_data JSON DEFAULT NULL;

4. 实施优先级总览

Phase 1(立即 — 让消息系统可用)
├── ✅ 让 MobileMessagesView.vue 接入 fetchNotifications
├── ✅ 底部导航栏增加未读消息角标
├── ✅ 增加 GET /api/notifications/unread-count API
├── ✅ 增加 POST /api/notifications/batch-read API
└── ✅ 消息列表支持点击跳转到对应订单详情

Phase 2(短期 — 完善通知体系)
├── notifications 表扩展 category/link_type/extra_data
├── 实现租期即将结束通知(站内信 + 前端定时检查)
├── 实现用户冻结/解冻通知
├── 短信 Provider 抽象层 + 限流器
├── sms_logs 表 + 短信日志后台查看
└── 接入一个真实短信服务商

Phase 3(中期 — 实时体验)
├── SSE Broker 实现
├── 订单状态实时推送接入前端
├── 租期倒计时实时更新
├── 客服消息实时通知
└── push_records 表(可选)

附录:关键文件索引

后端核心文件

文件 说明
backend/internal/modules/order/repository.go 订单 Repository,核心业务逻辑(1089行)
backend/internal/modules/order/handler.go 订单 HTTP Handler376行)
backend/internal/modules/order/service.go 订单 Service 层(160行)
backend/internal/modules/order/dto.go 订单 DTO 定义(126行)
backend/internal/model/order.go RentalOrder 模型定义
backend/internal/modules/wallet/repository.go 钱包 Repository + AppendEntries
backend/internal/modules/notification/repository.go 通知 Repository + Append
backend/internal/modules/dispute/repository.go 争议仲裁 Repository
backend/internal/jobs/ordertimeout/job.go 超时扫描定时任务

前端核心文件

文件 说明
frontend/src/views/mobile/MobileListingDetailView.vue 商品详情/下单页
frontend/src/views/mobile/MobileOrdersView.vue 订单列表页
frontend/src/views/mobile/MobileMessagesView.vue 消息页(空壳)
frontend/src/views/mobile/MobileProfileView.vue 个人中心(余额硬编码)
frontend/src/api/orders.ts 订单 API 定义
frontend/src/api/notifications.ts 通知 API 定义
frontend/src/api/wallet.ts 钱包 API 定义