酒店模块迁移跟踪.md 85 KB

关联文档需求说明书.html(需求基线 v1.0,2026-07-26,状态:需求确认中)|酒店模块需求缺口清单.md(需求对照 + P0/P1 缺口台账 + 补齐排期) 本文档职责:代码地图与实现进度,回答「代码里有什么」。需求视角的缺口、验收对照与排期不在本文档维护,避免双份状态互相失真。

1.3 Forge 已有代码

forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/
├── controller/
│   ├── HotelQrCodeController.java      ✅ 二维码管理 + 在住会话操作(入住/退房/打扫/维护/入住记录)
│   ├── HotelBusinessHoursController.java ✅ 营业时段管理(含 check-current 当前营业状态)
│   ├── HotelDishController.java        ✅ 菜品管理(分页/详情/增删改/上下架/售罄/恢复/批量/销量排行)
│   ├── HotelDishCategoryController.java ✅ 菜品分类管理(分页/详情/增删改/启禁用)
│   ├── HotelDishSpecController.java    ✅ 菜品规格管理(规格组 + 选项)
│   └── open/                           ✅ 免登录开放接口(SaTokenConfig 已排除 /hotel/open/** 的登录与 API 权限校验)
│       ├── HotelOpenController.java    ✅ 扫码查询 GET /hotel/open/scan
│       ├── HotelCustomerController.java ✅ 顾客端 9 接口(categoryEnabled/dishPage/dishDetail/orderCreate/orderDetail/orderPage/customerRefund/abandonOrder/businessHours)
│       ├── HotelPayController.java     ✅ 支付 5 接口(create/notify/status/alipayQuery/mockSuccess)
│       └── AlipayAuthController.java   ✅ 支付宝小程序用户授权 getUserInfo(姓名 + 手机号)
├── domain/
│   ├── HotelQrCode.java                ✅ 二维码实体
│   ├── HotelQrBindLog.java             ✅ 绑定日志实体
│   ├── HotelRoom.java                  ✅ 房间实体(⚠️ 无「启停点餐」字段)
│   ├── HotelRoomType.java              ✅ 房型实体
│   ├── HotelRoomStay.java              ✅ 在住会话实体(@TableLogic(value="0", delval="id"))
│   ├── HotelBusinessHours.java         ✅ 营业时段实体
│   ├── HotelDish.java                  ✅ 菜品实体(image 主图 + images 附加图)
│   ├── HotelDishCategory.java          ✅ 菜品分类实体
│   ├── HotelDishSpecGroup.java         ✅ 规格组实体(挂 dishId)
│   ├── HotelDishSpecOption.java        ✅ 规格选项实体(priceExtra 加价)
│   ├── HotelDishAddition.java          ✅ 加料实体(extraPrice 加价)
│   └── HotelDishLog.java               ⚠️ 菜品操作日志实体(表已建,**零业务代码**:无写入、无查询)
├── dto/
│   ├── QrCodeBatchGenerateDTO.java     ✅
│   ├── QrCodeBindDTO.java              ✅
│   ├── QrCodeQueryDTO.java             ✅
│   ├── HotelRoomDTO.java               ✅
│   ├── HotelRoomTypeDTO.java           ✅
│   ├── HotelDishDTO.java               ✅ 含 specGroups + additions 嵌套
│   ├── HotelDishCategoryDTO.java       ✅
│   └── DishBatchOperationDTO.java      ✅ 批量上下架/售罄
├── vo/
│   ├── HotelQrCodeVO.java              ✅
│   ├── HotelQrBindLogVO.java           ✅
│   ├── QrCodeScanVO.java               ✅ RoomInfo 含 roomStatus/stayId/guestName/guestPhone
│   ├── HotelRoomVO.java                ✅ 含 stayId/guestName/guestPhone/checkInTime
│   ├── HotelRoomTypeVO.java            ✅
│   ├── HotelRoomStayVO.java            ✅
│   ├── RoomCheckOutVO.java             ✅ needConfirm/message/activeOrders/checkedOut
│   ├── HotelDishVO.java                ✅ 含 specGroups + additions
│   ├── HotelDishCategoryVO.java        ✅
│   └── BusinessHoursStatusVO.java      ✅ 营业状态推导(configured/open/todayPeriods/nextText 等)
├── mapper/
│   ├── HotelQrCodeMapper.java          ✅
│   ├── HotelQrBindLogMapper.java       ✅
│   ├── HotelRoomMapper.java            ✅ XML 含 JoinActiveStay
│   ├── HotelRoomTypeMapper.java        ✅
│   ├── HotelRoomStayMapper.java        ✅ XML 显式 AND del_flag = 0
│   ├── HotelBusinessHoursMapper.java   ✅
│   ├── HotelDishMapper.java            ✅ XML 含 selectSalesRanking 销量排行
│   ├── HotelDishCategoryMapper.java    ✅
│   ├── HotelDishSpecGroupMapper.java   ✅
│   ├── HotelDishSpecOptionMapper.java  ✅
│   ├── HotelDishAdditionMapper.java    ✅
│   └── HotelDishLogMapper.java         ⚠️ 仅接口,无调用方
├── service/
│   ├── HotelQrCodeService.java         ✅
│   ├── HotelBusinessHoursService.java  ✅ 含 checkCurrentBusinessHours() + currentStatus()
│   ├── HotelRoomStayService.java       ✅ 入住/退房/打扫/维护/入住记录/联系人回写
│   ├── HotelDishService.java           ✅ 含 dishSalesRanking(limit)
│   ├── HotelDishCategoryService.java   ✅
│   ├── HotelDishSpecGroupService.java  ✅
│   ├── HotelDishSpecOptionService.java ✅
│   └── impl/                           ✅ 7 个实现类与接口一一对应
├── constant/
│   └── HotelQrConstants.java           ✅ 含 STAY_ACTIVE / STAY_CHECKED_OUT;`QR_BASE_PATH` 已降为「未配置时的回退默认值」(2026-09-07)
├── utils/
│   └── QrCodeUtils.java                ✅ `buildQrCodeUrl` 已拆三参(委派,兼容既有调用方)+ 四参(主实现,`basePath` 空串回退 `QR_BASE_PATH`)
└── notification/                       ✅ 实时通知子包
    ├── controller/
    │   └── NotificationController.java ✅ SSE 端点(subscribe/unsubscribe/connections/remote/notifyNewOrder)
    └── manager/
        └── SseEmitterManager.java      ✅ 长连接管理 + 广播(仅 NEW_ORDER / REFUND_REQUEST 两类事件)

> ⚠️ 文档修正:原记录的 `OrderNotificationService.java` **不存在**,通知广播能力由 `SseEmitterManager` 直接承担。

order/                                  ✅ 订单管理子包(独立于已有代码)
├── constant/
│   └── HotelOrderConstants.java        ✅ 订单状态常量 0~11 + 支付状态 0~3 + paySource + PAY_TIMEOUT_MINUTES=15 + 退单发起方 + refund_status 0~2
├── domain/
│   ├── HotelOrder.java                 ✅ 订单实体(含 payStatus/payExpireTime/paySource)
│   └── HotelOrderItem.java             ✅ 订单明细实体
├── dto/
│   └── OrderCreateDTO.java             ✅ 创建订单请求(roomNo/roomId/contactName/contactPhone/deliveryFee/deliveryTimeType/customDeliveryTime/note/items;已移除 payMethod,**无 paySource 字段**,paySource 在创建支付时传参)
├── vo/
│   ├── HotelOrderVO.java               ✅ 订单视图(含内部类 OrderItemVO)
│   └── OrderDashboardVO.java           ✅ 看板统计视图
├── mapper/
│   ├── HotelOrderMapper.java           ✅
│   └── HotelOrderItemMapper.java       ✅
├── service/
│   ├── HotelOrderService.java          ✅ 15 个方法接口(查询/看板/创建/5 步流转/拒单/3 类退单/审核/放弃);支付与超时取消在 pay 子包的 `HotelPayService`
│   └── impl/
│       └── HotelOrderServiceImpl.java  ✅ 完整状态机 + 退单流程 + 支付超时处理
├── controller/
│   └── HotelOrderController.java       ✅ 基路径 `/hotel/order`,15 个 REST 接口(`/page` `/detail` `/dashboard` `POST /` `/accept` `/reject` `/prepare` `/ready` `/deliver` `/complete` `/customerRefund` `/hotelRefund` `/refundApprove` `/refundReject` `/refund`)

pay/                                    ✅ 支付系统子包(支付宝沙箱 H5 WAP Pay + 安全加固 + 超时取消 + 退款 + Mock 开关)
├── config/
│   ├── AlipayConfig.java               ✅ 支付宝配置(appId/密钥/网关/回调地址,@ConfigurationProperties)
│   └── HotelPayConfig.java             ✅ 支付安全开关(`hotel.pay.mock-enabled`,**代码级默认 `false`**;`@PostConstruct` 打印开关状态,开启时 WARN 级资金安全告警)
├── domain/
│   └── HotelPayLog.java                ✅ 支付流水实体(outTradeNo/tradeNo/payAmount/payStatus/payChannel/paySource)
├── dto/
│   └── AlipayAuthDTO.java              ✅ 支付宝授权请求(authCode + phoneResponse)
├── vo/
│   ├── PayResultVO.java                ✅ 支付结果视图(payStatus/payForm/tradeNo)
│   └── AlipayAuthVO.java               ✅ 授权结果(userId/userName/phone)
├── mapper/
│   └── HotelPayLogMapper.java          ✅ @InterceptorIgnore + 幂等更新 + 按 orderId 查最新流水
│       (XML:resources/mapper/business/hotel/pay/HotelPayLogMapper.xml,幂等 UPDATE WHERE pay_status != 1)
├── service/
│   ├── HotelPayService.java            ✅ createPay/handlePayCallback/queryAlipayTradeStatus/refundOrder/cancelTimeoutOrder
│   ├── AlipayAuthService.java          ✅ 支付宝用户信息 + 手机号解密
│   └── impl/
│       ├── HotelPayServiceImpl.java    ✅ WAP Pay + 回调 + 退款 + 5 层安全加固 + `resolvePayChannel`/`mockOrReject` 渠道单一收口
│       └── AlipayAuthServiceImpl.java  ✅
└── task/
    └── PayTimeoutTask.java             ✅ @Scheduled(fixedDelay=120000) 每 2 分钟跨租户扫描超时未支付订单并取消(状态 10)

⚠️ 文档修正(3 处)

  1. 原记录的 pay/constant/HotelPayConstants.java 不存在,payStatus / paySource 常量统一放在 order/constant/HotelOrderConstants.java
  2. 原记录的 pay/dto/PayCreateDTO.java 不存在,创建支付为 createPay(Long orderId, String paySource) 直接传参。
  3. HotelPayControllerAlipayAuthController 实际位于 controller/open/ 包下, pay/controller/open/
forge-admin-ui/src/views/hotel/
├── board.vue                           ✅ 房态看板(房间卡片网格 + 状态 Tab + 入住/退房/打扫/维护/入住记录,V1.0.107 建菜单)
├── qrcode.vue                          ✅ 二维码管理(AiCrudPage + 批量生成/绑定/解绑/批量下载 ZIP)
├── room.vue                            ✅ 房间管理(AiCrudPage 纯 CRUD;⚠️ 在住业务操作已拆到 board.vue,编辑表单不含状态字段)
├── roomType.vue                        ✅ 房型管理(AiCrudPage)
├── dish.vue                            ✅ 菜品管理(AiCrudPage,图片预览/上下架/售罄;⚠️ 无加料与规格绑定编辑入口)
├── dishCategory.vue                    ✅ 菜品分类管理(AiCrudPage,含启禁用)
├── dishSpec.vue                        ✅ 菜品规格管理(AiCrudPage + 选项管理弹窗,规格组按 dishId 挂载)
├── businessHours.vue                   ✅ 营业时段管理(AiCrudPage,时间段下拉选择)
└── order.vue                           ✅ 订单管理(AiCrudPage + 看板统计 + 流程操作 + 详情弹窗 + 退单审核 + 退款状态列 + 手动退款 + SSE 通知)

forge-admin-ui/src/api/
└── hotel.js                            ✅ 酒店模块 API(房间/房型/二维码/菜品/分类/规格/营业时段/订单/退款/在住会话/销量排行)

forge-admin-ui/src/composables/
└── useOrderNotification.js             ✅ SSE 订阅 + MP3 提示音 + 桌面通知 + 防抖合并 + 首次交互补播

forge-h5-ui/src/pages/hotel/
├── scan-bind.vue                       ✅ 【员工 H5】扫码绑定房间(**双职责页**,见下方说明)
├── qr-scanner.vue                      ✅ 【员工 H5】扫码页(html5-qrcode,依赖 DOM + 摄像头)
└── customer/                            【顾客小程序】8 页(小程序化时仅保留这 8 页;`pages.json` L67-87 已用 `// #ifdef H5` 裁掉员工两页)
    ├── room-confirm.vue                ✅ 房号确认入口页(`resolveScanParams` 三端取参 + 扫码验证/房间卡片/开始点餐 + 🕐 非营业时段整页拦截)
    ├── menu.vue                        ✅ 点餐菜单页 Tab1(配送信息栏/Banner/搜索/分类/菜品列表/购物车浮层 + 🕐 非营业菜品区拦截)
    ├── dish-detail.vue                 ✅ 菜品详情页(大图/规格选择/加料/备注/数量/加入购物车)
    ├── cart.vue                        ✅ 购物车页 Tab3(商品列表/数量控制/备注/费用汇总/结算)
    ├── order-confirm.vue               ✅ 确认下单页(配送信息/送达时间/明细/提交 + 🕐 提交前营业状态刷新阻断,已移除硬编码支付方式)
    ├── pay.vue                         ✅ 支付确认页(按后端 `payChannel` 分流 + 支付宝 H5 WAP Pay + 轮询 + 主动查询兜底 + 倒计时;else 已拆为显式报错,不再兜底调 Mock)
    ├── order-status.vue                ✅ 订单状态页 Tab2(进度条/状态卡片/10秒轮询/自定义退单弹窗)
    └── orders.vue                      ✅ 订单列表页 Tab2(订单卡片列表/分页加载/状态标签/待支付去支付按钮)

forge-h5-ui/src/components/hotel/
└── HotelTabBar.vue                     ✅ 酒店顾客端 3-tab TabBar(点餐/订单/购物车);已按 `#ifdef H5` / `#ifdef MP` 拆双分支(146 行)

forge-h5-ui/src/styles/
└── hotel-theme.css                     ✅ 亚朵深墨绿主题 CSS 变量(--h-pri/--h-bg/--h-txt 等)

forge-h5-ui/src/store/modules/
└── hotel-order.js                      ✅ Pinia 购物车 Store(roomInfo/cartItems/orderNote/businessHours,持久化不含营业状态)

forge-h5-ui/src/api/
└── index.js                            ✅ 顾客端 API(扫码/分类/菜品/订单 CRUD/退单/放弃订单/支付/支付宝查询/支付宝授权/营业状态 businessHours)

⚠️ scan-bind.vue 是双职责页(共 812 行):用 authStore.isLogin 分流员工/顾客。

  • 未登录(顾客):loadScanInfo() L233-278 调 /hotel/open/scan,已绑定则 uni.redirectTocustomer/room-confirm,未绑定提示「二维码未启用」
  • 已登录(员工):展示绑定/解绑界面,调 hotelRoomListAll() / hotelQrBind() / hotelQrUnbind()(均需登录,操作前走 ensureLogin

员工扫码绑定房间已确认为员工登录 H5 后操作,不属于小程序(功能已完成)。小程序化时 scan-bind.vue + qr-scanner.vue#ifdef H5 整页排除,其独占的 AiIcon/AiButton/AiResult/html5-qrcode 自动不进小程序包。顾客小程序的扫码入口页是 customer/room-confirm.vue,不是 scan-bind.vue


二、模块迁移状态总览

# 模块 后端 数据库 前端 PC 前端 H5 状态
1 room(房间/房型管理) 已完成
2 qrcode(二维码管理) 已完成;✅ 二维码 URL 路径已配置化(hotel.qr.path:dev 保持旧 hash 值、生产默认 /r/,三层回退保证既有码 URL 逐字符不变,2026-09-07)
3 open(开放扫码接口) 已完成
4 dish-spec(菜品规格管理) 已完成
5 business-hours(营业时段) 已完成(后端 currentStatus 推导 + 开放接口 + H5 三页接入 + 下单校验)
6 hotelconfig(酒店基础配置) 暂缓(暂不需要,配送费 + 预计配送时长待讨论)
7 dish(菜品管理) ⚠️ 核心已完成(加料后端已通但 PC 无编辑入口;操作日志 仅实体+Mapper,零业务代码
8 order(订单管理) 主链路完成(后端+PC管理端+H5顾客端+支付系统);✅ 金额重算已计入规格/加料加价(2026-09-04)
8.1 pay(支付系统) ⚠️ 支付宝 H5 完成(沙箱 WAP Pay + 5 层安全加固 + 轮询兜底 + 退款 + 超时取消);✅ Mock 免付款资金红线已收口(2026-09-07:hotel.pay.mock-enabled 开关 + resolvePayChannel/mockOrReject 单一收口 + paySource 必传 + refundOrder 渠道显式判定 + pay.vue else 拆分);🔴 AGENTS.md 5.9 合规未闭环(代码先行、Spec 未补、人工审查未做);🔴 支付宝小程序支付 100% 不可用(无 ALIPAY_MP 渠道、my.tradePay 拿到 undefined tradeNo);⏸️ 微信支付暂缓,见缺口清单 3.3
8.2 customer-open(顾客端开放接口) 已完成(10 个免登录接口,requireTenantId() 统一校验);⏳ stayId 归属校验(R2)随小程序化收口,见缺口清单 3.2
8.3 alipay-auth(支付宝授权) 已完成且已调用getUserInfo(authCode → 姓名,加密响应 → 手机号),room-confirm.vue L376-396 已接入授权成功/取消/失败处理;✅ 手机号日志已脱敏(2026-09-07,符合 AGENTS.md 5.9)
9 notification(实时通知) ⚠️ 已完成(SSE + MP3 声音 + 桌面通知 + 防抖合并 + 跨服务转发);仅 NEW_ORDER / REFUND_REQUEST 两类事件,无接单/出餐通知
9.1 stay(在住会话 + 订单退款) 全部完成(入住/退房/打扫/维护 + 退房联动取消 + 自动/手动退款,见 3.10)
9.2 board(房态看板) 已完成(独立页面承接入住/退房/打扫/维护操作,V1.0.107 建菜单)
10 kitchen(厨房端) 未开发(无待备餐列表/无小票打印/无菜品语音播报)

🔴 标记项与全部未完成项的需求依据、证据链、修复方向与补齐排期统一记在 酒店模块需求缺口清单.md,本表只保留实现状态。


三、各模块详细任务清单

3.1 hotelconfig — 酒店基础配置 ⏸️ 暂缓

状态: 暂缓开发。暂时用不到,其中配送费预计配送时长功能待后续讨论后再实现。

后端(待开发)

  • 实体:HotelConfig.java — 酒店基础配置(名称/主题/配送费/预计配送时长)
  • Mapper: HotelConfigMapper.java + XML
  • Service: HotelConfigService.java — 获取/保存配置
  • Controller: HotelConfigController.java — 配置接口

数据库(待开发)

  • 建表:hotel_config(IF NOT EXISTS)
  • 菜单:酒店配置管理(sys_resource)

前端(待开发)

  • 页面:forge-admin-ui/src/views/hotel/config.vue — 酒店基础配置表单

⚠️ 待讨论项

  • 配送费规则:按距离/按区域/固定费用?是否支持阶梯定价?
  • 预计配送时长:固定值还是动态计算?单位分钟?
  • 这两项功能需要与业务方确认具体需求后再开发

源框架参考

文件 说明
hotelconfig/domain/HotelConfig.java 配置实体
hotelconfig/domain/HotelBusinessHours.java 营业时段实体
hotelconfig/controller/HotelConfigController.java 全部接口
hotelconfig/service/HotelConfigService.java 全部业务逻辑
hotelconfig/mapper/xml/HotelConfigMapper.xml 配置查询 SQL
前端:views/hotel/admin/HotelConfigManagement.vue 配置管理页面
前端:views/hotel/admin/BusinessHoursManagement.vue 营业时段页面
前端:views/hotel/admin/BusinessHoursForm.vue 营业时段表单
前端:api/hotel/HotelConfigService.js 配置 API
前端:api/hotel/HotelBusinessHoursService.js 营业时段 API

3.2 dish — 菜品管理 ⭐⭐⭐

后端

  • 实体:HotelDish.java — 菜品主表
  • 实体:HotelDishCategory.java — 菜品分类
  • 实体:HotelDishSpecGroup.java — 规格组(源框架同时存在 spec 和 spec_group,Forge 统一用 spec_group)
  • 实体:HotelDishSpecOption.java — 规格选项
  • 实体:HotelDishAddition.java — 加料
  • 实体:HotelDishLog.java — 操作日志
  • DTO: HotelDishDTO.java — 菜品传输对象
  • DTO: HotelDishCategoryDTO.java — 分类传输对象
  • DTO: DishBatchOperationDTO.java — 批量操作传输对象
  • VO: HotelDishVO.java / HotelDishCategoryVO.java — 视图对象
  • Mapper: HotelDishMapper.java + XML(含管理后台分页查询)
  • Mapper: HotelDishCategoryMapper.java + XML(含分页查询)
  • Mapper: HotelDishSpecGroupMapper.java
  • Mapper: HotelDishSpecOptionMapper.java
  • Mapper: HotelDishAdditionMapper.java + XML
  • Mapper: HotelDishLogMapper.java
  • Service: HotelDishService.java — 菜品 CRUD + 上下架 + 售罄/恢复 + 删除
  • Service: HotelDishCategoryService.java — 分类 CRUD + 启用/禁用
  • Service: HotelDishSpecGroupService.java — 规格组 CRUD + 级联删除选项
  • Service: HotelDishSpecOptionService.java — 规格选项 CRUD
  • Controller: HotelDishController.java — 菜品接口(分页/详情/新增/修改/删除/上下架/售罄/恢复)
  • Controller: HotelDishCategoryController.java — 分类接口(分页/新增/修改/删除/启禁用)
  • Controller: HotelDishSpecController.java — 规格组 + 规格选项统一接口
  • 加料:随菜品新增/修改一并保存(HotelDishDTO.additionssaveAdditions()),菜品详情回传 additions,H5 详情页可选加料并计算加价
  • ⚠️ PC 端无加料编辑入口dish.vue 表单不含加料与规格绑定项,等于加料数据无维护入口
  • ⚠️ 菜品操作日志未落地hotel_dish_log 表 + HotelDishLog 实体 + HotelDishLogMapper 已备,但无任何写入与查询代码(无需独立 Controller,应在 HotelDishServiceImpl 各操作埋点)

数据库

  • 建表:hotel_dish(IF NOT EXISTS)— 含 image(主图)+ images(附加图片)字段
  • 建表:hotel_dish_category(IF NOT EXISTS)
  • 建表:hotel_dish_spec_group(IF NOT EXISTS)
  • 建表:hotel_dish_spec_option(IF NOT EXISTS)
  • 建表:hotel_dish_addition(IF NOT EXISTS)
  • 建表:hotel_dish_log(IF NOT EXISTS)
  • 字典:hotel_dish_status(ON_SALE/OFF_SHELF)
  • 字典:hotel_dish_sold_out(0=正常/1=售罄)
  • 菜单:菜品管理 + 菜品分类管理(sys_resource)
  • 菜单权限:菜品增删改查按钮权限
  • 迁移脚本:V1.0.101__add_hotel_dish_module_tables.sql

前端

  • 页面:forge-admin-ui/src/views/hotel/dish.vue — 菜品管理(AiCrudPage)
    • 序号列、主图缩略图(AuthImage)、全屏图片预览(主图 + 副图左右切换)
    • 编辑按钮(仅下架可编辑)、上架/下架、售罄/恢复、删除
    • 编辑表单:主厨推荐 (Switch)、状态 (Radio)、价格/制作时长/排序 (inputNumber)、主图/附加图片 (imageUpload)
  • 页面:forge-admin-ui/src/views/hotel/dishCategory.vue — 菜品分类管理(AiCrudPage)
    • 编辑按钮、启用/禁用操作
  • API: forge-admin-ui/src/api/hotel.js 中追加菜品/分类相关接口

源框架参考

文件 说明
dish/domain/HotelDish.java 菜品实体
dish/domain/HotelDishCategory.java 分类实体
dish/domain/HotelDishSpecGroup.java 规格组实体
dish/domain/HotelDishSpecOption.java 规格选项实体
dish/domain/HotelDishAddition.java 加料实体
dish/domain/HotelDishLog.java 日志实体
dish/controller/HotelDishController.java 菜品全部接口
dish/controller/HotelDishCategoryController.java 分类接口
dish/controller/HotelDishSpecGroupController.java 规格组接口
dish/controller/HotelDishSpecOptionController.java 规格选项接口
dish/service/HotelDishService.java 菜品业务逻辑(417 行)
dish/service/HotelDishCategoryService.java 分类业务逻辑
dish/mapper/xml/HotelDishMapper.xml 菜品 SQL
dto/DishBatchOperationDTO.java 批量操作 DTO
前端:views/hotel/dish/DishManagement.vue 菜品管理页面
前端:views/hotel/dish/DishForm.vue 菜品表单
前端:views/hotel/dish/CategoryManagement.vue 分类管理
前端:views/hotel/dish/CategoryForm.vue 分类表单
前端:api/hotel/HotelDishService.js 菜品 API
前端:api/hotel/HotelDishSpecService.js 规格 API

注意事项

  • 源框架同时存在 hotel_dish_spechotel_dish_spec_group 两张表,Forge 统一使用 hotel_dish_spec_group
  • 源框架删除菜品时校验未完成订单 → 需要依赖 order 模块(可先预留校验接口,order 完成后再联通)
  • 菜品价格使用 BigDecimal,金额单位与源框架保持一致(元)

3.3 order — 订单管理 ⭐⭐⭐⭐(最复杂)

状态: 全部完成。后端 + 前端 PC 管理端 + H5 顾客点餐端 + 支付系统均已完成。

后端

  • 实体:HotelOrder.java — 订单主表(Integer status,0~10 数字状态,含 payStatus/payExpireTime/paySource)
  • 实体:HotelOrderItem.java — 订单明细(冗余 dishName/specDesc/additionsDesc)
  • DTO: OrderCreateDTO.java — 创建订单请求(已移除 payMethod,改用 paySource)
  • VO: HotelOrderVO.java — 订单视图对象(含内部类 OrderItemVO + itemCount)
  • VO: OrderDashboardVO.java — 看板统计响应
  • Mapper: HotelOrderMapper.java + XML(含看板统计/今日营收/订单号生成,6 个自定义 SQL)
  • Mapper: HotelOrderItemMapper.java + XML(按订单ID查询 + 批量插入)
  • Service: HotelOrderService.java — 14+ 个方法接口(含完整 Javadoc + 支付/超时取消)
  • Service: HotelOrderServiceImpl.java — 完整状态机 + 退单流程 + 菜品销量累加 + 支付超时处理
  • Controller: HotelOrderController.java — 15+ 个 REST 接口(查询3 + 操作6 + 退单4 + 创建1 + 看板1 + 支付相关)
  • 常量:HotelOrderConstants.java — 状态 0~10 + 退单发起方

数据库

  • 建表:hotel_order(IF NOT EXISTS)— 23+ 个字段 + 4 个索引(V1.0.104 建表 + V1.0.105 支付字段扩展)
  • 建表:hotel_order_item(IF NOT EXISTS)— 13 个字段 + 1 个索引
  • 字典:hotel_order_status(0~10 数字状态,对应中文标签 + list_class 颜色,V1.0.104 + V1.0.105 补充 0/10)
  • 字典:hotel_pay_method(MOCK/CASH/ONLINE/CREDIT)
  • 字典:hotel_delivery_time_type(ASAP/CUSTOM)
  • 菜单:订单管理(sys_resource)
  • 菜单权限:订单查询/接单/拒单/备餐/配送/退单审核 6 个按钮权限
  • 迁移脚本:V1.0.104__add_hotel_order_module.sql(建表 + 字典 + 菜单)
  • 迁移脚本:V1.0.105__add_hotel_order_payment_fields.sql(支付字段 + 状态 0/10 + MySQL 8 兼容)

前端

  • 页面:forge-admin-ui/src/views/hotel/order.vue — 订单管理(AiCrudPage + 看板统计 + 流程操作 + 详情弹窗)
    • 顶部看板统计卡片(待接单/备餐中/配送中/今日订单/今日营收)
    • 搜索筛选(房间号/订单状态/联系人/支付方式)
    • 操作列按状态动态显示(接单/拒单/备餐/出餐/配送/完成/同意退单/驳回/前台退单)
    • 订单详情弹窗(基本信息 + 金额 + 时间记录 + 菜品明细表格)
    • 原因输入弹窗(拒单/退单审核共用)
  • API: forge-admin-ui/src/api/hotel.js 中追加 14 个订单接口函数

支付系统(pay 子包)⭐⭐⭐

状态: 支付宝 H5 WAP Pay 全链路完成(沙箱)+ 5 层安全加固 + 前端轮询兜底。 ✅ Mock 免付款资金红线已收口(2026-09-07):hotel.pay.mock-enabled 开关(代码级默认 false,yml 整块缺失也不会误开)+ resolvePayChannel/mockOrReject 单一收口(全仓禁止裸 return "MOCK")+ paySourcedefaultValue + refundOrder 渠道显式判定 + pay.vue else 拆分。dev 环境 application-dev.yml 覆盖为 true既有 H5 调试行为零变化。 ⚠️ AGENTS.md 5.9 合规状态:Spec 已补写、人工审查待执行(2026-09-07):本节属资金类变更,代码先于 Spec 落地,属流程倒置。已补写 code-copilot/changes/hotel-pay-mock-hardening/spec.md(13 节结构,status: review)+ tasks.md(8 Task 全标已执行);第 8.1 节摊开三个待审查资金风险点、第 10.1 节登记与原方案三处差异、第 11 节回填实际改动文件。第 13 节 HARD-GATE 的确认时间/确认人留空,只能由需求方本人手填,签署前不得归档。 🔴 支付宝小程序支付尚未打通(无 ALIPAY_MP 渠道,见下方「小程序支付缺口」);⏸️ 微信支付暂缓(渠道已显式阻断,不再静默降级为 MOCK)。 ✅ 【已决策接受】支付宝沙箱密钥明文入库application-dev.yml 含支付宝沙箱 private-key / alipay-public-key 明文,已随 commit 65508d9 推到 origin/master用户决策:git 是内网私有仓库(192.168.8.4),不在外网;密钥是沙箱密钥;后期如有必要,会直接不提交生产的密钥。本变更关闭(hotel-alipay-secret-externalize 不再推进)。 ✅ 【已撤销】生产 profile 启动崩溃 — 判断错误,当前不会崩AlipayConfig L22-34 有 5 个 @Value 完全无默认值,而两份生产 application.yml 完全没有 hotel.alipay原判断:生产启动即崩(P0)。更正application.yml L42-43 spring.profiles.active: dev,且 docker-compose.yml / .env.example 无任何 profile 覆盖,项目也没有 application-prod.yml。因此当前启动(包括 docker 部署)都会加载 application-dev.ymlhotel.alipay.* 全部有值,不会崩。我错在把 application.yml 当成「生产专用配置」,忽略了它 L43 默认激活 dev profile。 需求视角的缺口与修复排期见 酒店模块需求缺口清单.md 3.3 / 3.6.5。

后端
  • 配置:AlipayConfig.java — 支付宝配置(appId/privateKey/alipayPublicKey/gatewayUrl/notifyUrl/returnUrl/sandbox)
    • ⚠️ 文档修正:原记「@ConfigurationProperties 注入」错误,实为 7 个独立 @Value 字段注入(L22-41)
    • 【已撤销】P0 隐患:其中 5 个 @Value 无默认值,但 application.yml L42-43 spring.profiles.active: dev,当前启动会加载 application-dev.ymlhotel.alipay.* 全部有值,不会崩。原判断错误(把 application.yml 当生产专用)
    • alipayClient() L50-54 — @Bean 无条件创建 DefaultAlipayClient,密钥为空时不会启动报错但调用时验签失败,错误信息晦涩
    • 【已决策接受】密钥明文入库:L25 private-key 的实际值写在 application-dev.yml,已进远端历史。用户决策:内网私仓 + 沙箱密钥,可接受;后期生产密钥不提交
  • 配置:pay/config/HotelPayConfig.java支付安全开关(2026-09-07 新增):@Value("${hotel.pay.mock-enabled:false}") 代码级默认 false@PostConstruct logMockSwitch() 启动即打印开关状态,开启时用 WARN 输出「【资金安全告警】」,避免误配到生产无人察觉
  • 常量:HotelPayConstants.java — 支付状态常量(0=待支付/1=成功/3=超时)+ 支付渠道(MOCK/WECHAT/ALIPAY)
  • 实体:HotelPayLog.java — 支付流水(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyContent)
  • DTO: PayCreateDTO.java — 创建支付请求(orderId + paySource)
  • VO: PayResultVO.java — 支付结果(payStatus/payForm/tradeNo)
  • Mapper: HotelPayLogMapper.java + XML
    • @InterceptorIgnore(tenantLine = "true") 绕过租户拦截(回调无登录态)
    • updatePayStatusSuccessByIdempotent — 数据库级幂等更新(UPDATE WHERE pay_status != 1
    • selectLatestByOrderId — 按订单 ID 查最新支付流水(主动查询兜底用)
  • Service: HotelPayService.java — 支付接口(createPay/handlePayCallback/queryAlipayTradeStatus/queryPayStatus)
  • Service: HotelPayServiceImpl.java — 完整支付实现
    • resolvePayChannel(paySource) L821-836 — 渠道解析单一收口点(2026-09-07 新增):ALIPAY/ALIPAY_MPALIPAY;其余全部分支(WECHAT/WECHAT_MP/空值/MOCK/H5/未知)一律走 mockOrReject(reason)
    • ⚠️ 语义陷阱resolvePayChannel("ALIPAY_MP") 返回 "ALIPAY"(两者归为同一渠道),因此后续 trade.create 分支必须用原始 paySource 分流payChannel 已无法区分
    • mockOrReject(reason) L846-851 — 全仓唯一允许返回 "MOCK" 的位置if (hotelPayConfig.isMockEnabled()) return "MOCK"; throw new BusinessException(reason);。改造前有两处静默降级裸 return "MOCK",正是免付款漏洞成因
    • createPay()渠道解析已前置(L117-119):非白名单渠道在建流水前即被拒,杜绝静默降级;后续分流用已解析的 payChannel(L138-140),禁止用原始 paySource 判断(否则 ALIPAY_MP 会漏进 else 被置为 MOCK)
    • ALIPAY → AlipayTradeWapPayRequest.pageExecute() → 返回 payForm HTML
    • ⚠️ pageExecute()本地签名即成功、不调网关,用户实际付款前不创建交易,因此没有 tradeNo
    • 🔴 小程序支付缺口my.tradePay({ tradeNO }) 只能配 alipay.trade.create。需新增 ALIPAY_MP 渠道走 AlipayTradeCreateRequest 返回 tradeNobuyer_idAlipayAuthController#getUserInfo 拿到的支付宝 userId 提供
    • handlePayCallback() — 支付宝异步回调处理(拆分两步:查 payLog 获取 tenantId → TenantContextHolder.executeWithTenant 包裹业务处理)
    • queryAlipayTradeStatus() — 主动查询支付宝侧订单状态(回调丢失时的兜底机制)
  • Controller: controller/open/HotelPayController.java — 5 个接口(❗ 位于 controller/open/,非 pay/controller/
    • POST /hotel/open/pay/create — 创建支付(返回 payForm/tradeNo)
    • ✅ L56-58 @RequestParam Long tenantId, @RequestParam Long orderId, @RequestParam String paySource —— 已去掉 required = false + defaultValue = "MOCK",不传即 400(2026-09-07)
    • POST /hotel/open/pay/notify — 支付宝异步回调(签名验证 + 金额校验 + app_id 校验 + 幂等更新 + 异常返回 fail 触发重试)
    • GET /hotel/open/pay/status — 查询支付状态(前端轮询用)
    • GET /hotel/open/pay/alipayQuery — 主动查询支付宝侧订单状态(兜底机制)
    • POST /hotel/open/pay/mockSuccess — Mock 支付(仅开发调试用)
    • 资金红线已收口(2026-09-07):HotelPayController.mockPaySuccess() L83-86 方法首行拦截 —— if (!hotelPayConfig.isMockEnabled()) { log.warn(...); throw new BusinessException("模拟支付已禁用"); }。接口仍位于 /hotel/open/** 白名单(SaTokenConfig L79/L104 整体放行,免登录属性未变),但开关默认 false 后生产环境调用一律报错
    • 💡 未采用「给接口加鉴权」方案:开放接口无法携带登录态(顾客未登录),改用环境开关 + 渠道白名单双层防护
    • ⚠️ 文档修正:原文记录的 payNotify 实为 notifyreturnUrl 接口不存在AlipayConfig.returnUrl 仅作为支付宝同步跳转地址配置,回到 H5 后由前端轮询检测状态)
  • Controller: controller/open/AlipayAuthController.javaPOST /hotel/open/alipay-auth/getUserInfo(authCode → 姓名,加密响应 → 手机号)
    • 已被前端调用(纠正旧记录「当前 H5 未调用」):room-confirm.vue L376-396 已实现授权成功/取消/失败三分支处理
    • 日志脱敏已做(2026-09-07):手机号不再明文入日志,符合 AGENTS.md 5.9「禁止在日志中打印手机号」
    • 💡 小程序化时该接口是 buyer_id / user_id → stayId 绑定的数据来源,是收口 R2 的关键链路
  • Task: pay/task/PayTimeoutTask.java — 每 2 分钟跨租户扫描超时未支付订单(selectExpiredUnpaidOrderIdscancelTimeoutOrder,状态 10)
  • 退款:HotelPayService.refundOrder() L564-582 — 支付宝 AlipayTradeRefundRequest 全额退款,失败标记 refund_status=2 可重试
    • 渠道显式判定已落地(2026-09-07,防资损):paySource=MOCK 流水 pay_channel=MOCK 无流水 → 仅回写退款状态(markRefundSuccess);ALIPAY → 走真实退款;其余渠道 → log.error + 抛「该支付渠道暂不支持在线退款,请联系前台人工处理」
    • 🔴 改造前用 !"ALIPAY".equals(payChannel) 兜底判 MOCK —— 未来接入微信后会造成真实资损(微信订单退款被误判为无资金流而直接标记成功),已改为显式判定 MOCK
数据库
  • 建表:hotel_pay_log(IF NOT EXISTS)— 支付流水表(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyTime/notifyContent)
  • 迁移脚本:V1.0.106__add_hotel_pay_log_table.sql
安全加固(5 层保障)
# 保障层 说明
1 RSA2 签名验证 AlipaySignature.rsaCheckV1 验证回调签名,防伪造
2 app_id 校验 验证回调中的 app_id 与配置一致,防跨应用攻击
3 金额校验 回调金额与订单金额比对,不一致则拒绝
4 数据库级幂等 UPDATE WHERE pay_status != 1 利用行锁保证原子性,防并发重复处理
5 主动查询兜底 前端轮询超时后调用 /alipayQuery 主动查支付宝侧状态,补偿丢失的回调
异常场景覆盖
场景 防护措施
事务处理中断电 @Transactional 保证原子性,全部回滚
回调时网络中断 返回 "fail",支付宝 24h 内自动重试(约 8 次)
并发重复回调 数据库 UPDATE WHERE pay_status != 1 行锁幂等
回调全部丢失 前端 /alipayQuery 主动查支付宝侧订单状态
服务器长时间宕机 支付宝重试 + 恢复后自动处理
前端(pay.vue)
  • 支付宝 H5 WAP Pay:后端返回 payForm(HTML 表单),前端渲染后自动提交跳转支付宝收银台
  • ⚠️ 支付环境「自动识别」实为前端判断,非服务端环境判断:条件编译(MP-WEIXIN/MP-ALIPAY/H5)+ UA 检测(micromessenger)得出 paySource,由前端传参给后端,未做服务端校验(需求 7.安全「防支付方式注入」未满足)
  • 🔴 支付宝小程序支付 100% 不可用(纠正旧记录):后端只 put 了 payFormtradeNoundefinedmy.tradePay({ tradeNO }) 必然 fail → 提示「支付取消或失败」、paying 复位。不是免付款下单
    • ✅ 分流依据已从 paySource 改为后端返回的 createData.payChannel(L204-286,2026-09-07):因为 resolvePayChannel("ALIPAY_MP") 返回 "ALIPAY",用 paySource 判断会让小程序分支永远进不了支付宝分支
  • Mock 免付款兜底已拆除(2026-09-07):原 L258-276 else 分支直接调 hotelPayMockSuccess(显示「支付成功」但无真实扣款)。采取「拆分」而非「删除」
    • 新增独立分支 else if (createData.payChannel === 'MOCK') 才调 hotelPayMockSuccess —— 后端开关为 true 时才会返回 payChannel=MOCK,因此该分支生产环境永远不会命中,既有 H5 调试行为保持不变
    • 兜底 else 改为 uni.showModal「暂无法在线支付」+ paying 复位,禁止在此兜底调 Mock(那等于绕过资金校验免付款)
  • 递归 setTimeout 轮询:首次延迟 3 秒,间隔 2 秒,最多 60 次(120 秒),检测支付状态
  • 轮询超时兜底:超时后调用 /alipayQuery 主动查询支付宝侧订单状态
  • 页面加载自动轮询:从支付宝返回后自动检测支付状态(无需用户操作)
  • 已支付直接跳转:订单 payStatus=1 时直接跳转订单状态页

H5 顾客端(forge-h5-ui)

  • 基础设施:
    • api/index.js — 新增 12+ 个顾客端 API 函数(含支付/支付宝查询)
    • store/modules/hotel-order.js — Pinia 购物车 Store(roomInfo/cartItems/orderNote,persist 持久化)
    • styles/hotel-theme.css — 亚朵深墨绿主题 CSS 变量(--h-pri: #2D5016 等)
    • components/hotel/HotelTabBar.vue — 3-tab 自定义 TabBar(点餐/订单/购物车),使用 uni.reLaunch 导航(已移除"我的"tab,避免与系统 tabBar 冲突)
    • ✅ 已按 #ifdef H5 / #ifdef MP 拆双分支(146 行,2026-09-07):H5 保留原 WebkitMask + 本地 SVG;小程序侧改原生 <image>
    • 🔴 小程序图标无法换色:lucide 风格 SVG 的 stroke="currentColor" 在小程序 <image>解析为黑色且无法换色 → 激活态只能用 opacity: 1 / 0.45 区分;已排除 ?raw + data-URI 方案(全仓 grep 零命中,无先例)
    • pages.json — 注册 8 个顾客端页面路由
    • ✅ L67-87 已用 // #ifdef H5 / // #endif 裁掉员工两页(pages-hotel-scan-bind / pages-hotel-qr-scanner,2026-09-07)。已用 pnpm build:h5 产物验证:H5 仍含这两个 chunk,dev 零影响
    • 💡 页面被 pages.json 裁掉后不参与编译.vue 内部无需再加 #ifdef;真正要保护的是其它页面对它的入口引用
    • 首页入口引用已同步git diff 核实在位):pages/index/index.vue 的 5 处 #ifdef H5 —— scanBindItem 常量 L245-254 / SCAN_BIND_PERM L274-276 / menuItemsprefix 逻辑 L282-288 / isRegisteredH5Route 白名单 L412-427 / handleShortcut 跳转 —— 全部在位 → 小程序构建时首页不渲染该入口,不会 navigateTo 到未注册页面。H5 dev 零变化
    • 🔴 <Teleport> 不支持小程序(实测推翻原判断):pnpm build:mp-alipay 2.4s 内失败AiAvatarCropper.vue<Teleport to="body">[vite:vue] not supported: Teleport)。根因:unplugin-vue-components 会为任何被引用到的组件生成注册代码,与「顾客 8 页是否引用」无关 → 页面裁剪挡不住
    • .env.mp-alipay 已生效package.json L9/L25 的 --mode mp-alipay 在位(git diff 核实)。⚠️ 该 --mode 不可省 —— 实测 @dcloudio 包内loadEnv( 调用,uni-app/Vite 只按 mode 加载 .env.[mode]不会按平台加载 .env.[platform],去掉 --mode 会让该文件静默失效。manifest.json L61-64 的 mp-alipay.appid 占位亦在位(值为空串,待资质),三项构成完整链条、无遗留不一致
  • 页面:forge-h5-ui/src/pages/hotel/customer/room-confirm.vue — 房号确认入口页
    • resolveScanParams(options) L185-232 — 三端取参已落地(2026-09-07):H5 直开路径读 c/s齐备即返回,不走正则,dev 零变化);小程序扫码从 query.q(微信)/ query.qrCode(支付宝)decodeURIComponent 后正则提取;取参源为 getCurrentPages() 末页的 $page.options || options
    • ⚠️ 与早期方案稿的两处实质差异:① 返回 {shortCode, sign} 而非 {c,t,s}tenantId 由后端按 shortCode 反查后经 data.tenantId 回传(L270);② 显式参数优先
    • 扫码验证房间(调用 /hotel/open/scan 免登录接口)
    • 房间卡片展示(房间号 + 房型)、验证成功动画、开始点餐按钮
  • 页面:forge-h5-ui/src/pages/hotel/customer/menu.vue — 点餐菜单页(Tab1)
    • 配送信息栏(房间号/房型)、Banner、搜索框
    • 分类标签横向滚动、菜品列表(图片/价格/加入购物车)
    • 购物车浮层(数量角标/总价/去结算)
  • 页面:forge-h5-ui/src/pages/hotel/customer/dish-detail.vue — 菜品详情页
    • 大图展示、规格选择(必选/可选标签)、加料选择(多选)
    • 特殊要求备注、数量控制、加入购物车(含规格合并逻辑)
  • 页面:forge-h5-ui/src/pages/hotel/customer/cart.vue — 购物车页(Tab3)
    • 商品列表(图片/规格描述/数量控制)、订单备注
    • 费用汇总(菜品小计 + 配送费)、结算按钮
    • 已修复: 结算按钮添加 box-sizing: border-box 防止越界
  • 页面:forge-h5-ui/src/pages/hotel/customer/order-confirm.vue — 确认下单页
    • 配送信息(房间号/联系人/联系电话)、送达时间选择(立即/30分钟/1小时)
    • 订单明细、提交订单(调用 /hotel/order 创建)
    • 已修复: unitPrice 字段从 item.price(基础价)改为 item.unitPrice(含规格/加料加价)
    • 已清理: 移除硬编码的"支付方式"卡片(非小程序自动适配)
    • 已修复: 提交按钮添加 box-sizing: border-box 防止越界
  • 页面:forge-h5-ui/src/pages/hotel/customer/pay.vue — 支付确认页
    • 金额展示(从后端 API 获取真实金额,非 URL 参数)
    • 支付宝 H5 WAP Pay(payForm 渲染 + 自动提交跳转收银台)
    • ⚠️ 支付环境识别为前端判断(条件编译 + UA 检测);🔴 MP-ALIPAY 分支拿到 undefined tradeNo 必然失败,待新增 ALIPAY_MP 渠道后拆清分支(见上方「小程序支付缺口」)
    • 递归 setTimeout 轮询(首次 3 秒延迟,间隔 2 秒,最多 120 秒)
    • 轮询超时兜底(主动查询支付宝侧订单状态)
    • 返回修改按钮(redirectTo 到菜单页,因购物车已清空)
    • loading 状态、支付倒计时
    • 已清理: 移除"支付方式"卡片(非小程序自动适配)
    • 已修复: 支付按钮添加 box-sizing: border-box 防止越界
  • 页面:forge-h5-ui/src/pages/hotel/customer/order-status.vue — 订单状态页(Tab2)
    • 状态大卡片(图标 + 文本 + 提示,渐变色按状态区分)
    • 水平进度条(已下单→已接单→备餐中→已出餐→配送中→已送达)
    • 退单/拒单通知卡片(显示原因)
    • 配送信息 + 订单明细 + 订单信息三个区块
    • 10 秒轮询自动刷新(未完成订单)
    • 退单申请自定义弹窗(标题 + 提示文案 + 原因输入框 + 取消/确定按钮,替代 uni.showModal)
  • 页面:forge-h5-ui/src/pages/hotel/customer/orders.vue — 订单列表页(Tab2)
    • 订单卡片列表(订单号/状态标签/菜品摘要/时间/金额)
    • 分页加载(pageNum + pageSize)、退单原因显示
    • 待支付订单(status=0)显示"去支付"按钮
    • 点击待支付订单跳转支付页,其他订单跳转订单状态详情

源框架参考

文件 说明
order/domain/HotelOrder.java 订单实体
order/domain/HotelOrderItem.java 订单明细实体
order/controller/HotelOrderController.java 全部接口(430 行)
order/service/HotelOrderService.java 订单业务逻辑(749 行)
order/mapper/xml/HotelOrderMapper.xml 订单 SQL
order/mapper/xml/HotelOrderItemMapper.xml 明细 SQL
dto/OrderCreateDTO.java 创建订单 DTO
dto/OrderDashboardDTO.java 看板 DTO
前端:views/hotel/hotelfront/HotelFrontOrders.vue 前台订单页面
前端:views/hotel/kitchen/KitchenOrders.vue 厨房订单页面
前端:views/hotel/restaurant/RestaurantOrders.vue 餐厅订单页面
前端:views/hotel/customer/CustomerMenu.vue 顾客点餐页面
前端:api/hotel/HotelOrderService.js 订单 API

订单状态机(数字编号 1~9)

0-PENDING_PAY(待支付)
  └── → 超时自动取消 → 10-TIMEOUT_CANCELLED(超时取消)
  └── → 支付成功 → 1-PLACED(待接单)

1-PLACED(待接单)
  ├── → 2-ACCEPTED(已接单)→ 3-PREPARING(备餐中)→ 4-READY(已出餐)→ 5-DELIVERING(配送中)→ 6-COMPLETED(已完成)
  ├── → 7-REJECTED(已拒单)
  └── → 9-REFUNDED(已退单,未接单时直接退)

2-ACCEPTED(已接单)
  └── → 8-REFUND_REQUESTED(退单待审核)
         ├── → 9-REFUNDED(审核通过)
         ── → 2-ACCEPTED(审核驳回,恢复)

注意事项

  • 源框架使用 synchronized 防止并发接单 → Forge 当前版本未加锁,后续高并发场景需补充 Redisson 分布式锁
  • 🔴 金额重算不完整(资金缺陷,待修)已修复(2026-09-04):hotel_order_item 已增 spec_option_idsaddition_ids 字段(迁移脚本 V1.0.109),order-confirm.vue L248-249 提交时携带 ID,orderCreate() L198-216 按 ID 从数据库查规格/加料价格重算 unitPrice。验证:支付页金额 = 下单页金额 = 购物车金额。验收结论见缺口清单 3.1。⚠️ 原记录的 V1.0.104order-confirm.vue L141-148、orderCreate() L158-175 与不存在的变更目录 code-copilot/changes/hotel-order-amount-fix/ 均已更正/删除
  • 🔴 配送费信任前端传值已关闭(2026-09-04):已从 OrderCreateDTO 删除 deliveryFee 字段(前端传值入口彻底消失),改由 hotel_config.delivery_fee 服务端读取;负数归零、超 999.00 截断并告警;V1.0.112 播种缺省 0.00
  • 🔴 Mock 支付免登录暴露(资金红线,待修)已收口(2026-09-07):hotel.pay.mock-enabled 开关(代码级默认 false)+ HotelPayController.mockPaySuccess() L83-86 首行拦截 + resolvePayChannel/mockOrReject 单一收口 + paySourcedefaultValue + refundOrder L564-582 渠道显式判定 + pay.vue L204-286 else 拆分。dev 环境覆盖为 trueH5 调试行为零变化
    • ⚠️ AGENTS.md 5.9 合规:Spec 已补写、审查待执行(代码先行属流程倒置)。code-copilot/changes/hotel-pay-mock-hardening/spec.md(13 节,status: review)+ tasks.md 已于 2026-09-07 补齐,HARD-GATE 确认时间/确认人留空待需求方本人手填,签署前本项不算真正关闭。详见缺口清单 3.3 / 3.3.1
  • 🔴 支付宝小程序支付不可用(待修):无 ALIPAY_MP 渠道,my.tradePay 拿到 undefined tradeNo 必然 fail。详见缺口清单 3.6.5,第 3 批修复
    • ⚠️ 修复时注意:resolvePayChannel("ALIPAY_MP") 已归并返回 "ALIPAY",新增 trade.create 分支必须用原始 paySource 分流,用 payChannel 无法区分 H5 与小程序
  • ✅ 源框架订单完成后自动累加菜品销量 → 已实现(incrementDishSales);销量排行接口 GET /hotel/dish/ranking 与前端 getDishSalesRanking() 已就绪,但无页面调用
  • ✅ 源框架退单流程较复杂(客户退单 + 前台退单 + 审核退单)→ 已完整迁移
  • 订单状态使用数字 0~11(0=待支付, 1~9 原有状态, 10=超时取消, 11=退房联动取消),非英文字符串常量
  • 金额使用 BigDecimal + 元(与 dish 模块一致),编码规范要求 bigint + 分但为模块一致性保持 DECIMAL

3.4 notification — 实时通知 ⭐⭐

状态: 已完成。后端 SSE 通知 + 前端声音提醒 + 桌面通知 + 防抖合并。

后端

  • Manager: SseEmitterManager.java — SSE 长连接管理 + 广播(ConcurrentHashMap + AtomicInteger)+ 远程接收(receiveNewOrderFromRemote),直接承担通知广播职责
  • Controller: NotificationController.java — SSE 端点(subscribe/unsubscribe/connections/remote/notifyNewOrder)
  • ⚠️ 文档修正:原记录的 OrderNotificationService.java(notifyNewOrder + notifyRefundRequest)不存在,该能力已内聚到 SseEmitterManager
  • ⚠️ 事件类型仅 NEW_ORDERREFUND_REQUEST 两类;无接单/出餐通知事件(需求 4.2「出餐通知接收」未满足)
  • 集成: HotelPayServiceImpl.java — 支付成功后发布 NEW_ORDER 通知(支持跨服务 HTTP 调用)
  • 集成: HotelOrderServiceImpl.java — 客户申请退单时发布 REFUND_REQUEST 通知
  • 跨服务通信: 8583(AppServer)支付成功后通过 HTTP POST 调用 8580(AdminServer)/hotel/notification/remote/notifyNewOrder 接口,解决双服务 SSE 内存不共享问题
  • Sa-Token 白名单: 添加 /hotel/notification/remote/** 到免登录路径(服务间调用无登录态)

数据库

  • 无需新建表(直接通过 SSE 推送,不使用 Redis Pub/Sub,单 JVM 足够)

前端

  • Composable: useOrderNotification.js — SSE 订阅 + MP3 声音 + 桌面通知 + 防抖合并 + Chrome 自动播放策略绕过
  • 集成: order.vue — 接入 SSE 通知(声音提醒 + Naive UI 通知 + 看板/列表自动刷新 + 连接状态指示器 + 页面刷新自动检测待接单订单)
  • 音频文件: forge-admin-ui/public/女生-订单提醒.mp3 — MP3 语音提醒文件

技术决策

  • 不使用 Redis Pub/Sub(单 JVM 部署,直接调用 SseEmitterManager 即可,省去中间层)
  • SSE 端点使用 @SaIgnore 跳过登录校验(EventSource 无法携带 Authorization Header)
  • 声音使用 MP3 文件(/女生-订单提醒.mp3),通过 Audio 对象播放
  • 3 秒防抖窗口合并多个同时到达的通知(避免弹窗叠加)
  • 绿色呼吸灯指示 SSE 连接状态(看板工具栏右侧)
  • 跨服务通信:PC端(8580 AdminServer)和移动端(8583 AppServer)是独立 JVM 进程,SSE 连接存储在各自内存中。8583 支付成功后通过 HTTP POST 调用 8580 的 /hotel/notification/remote/notifyNewOrder 接口,由 8580 推送 SSE 事件给 PC 端
  • 服务类型判断:通过 @Value("${server.type:admin}") 注入自定义配置属性判断当前服务类型(adminapp),在两个服务的 application.yml 中分别配置 server.type: admin/app
  • Chrome 自动播放策略:页面加载时用户未交互,audio.play() 会被阻止。通过监听首次 click/keydown 事件,将播放失败的声音加入待播放队列,用户首次交互后自动补播
  • 页面刷新触发语音:通过 watch(connected, ...) 监听 SSE 连接状态,连接成功后自动查询待接单订单(status=1)并触发语音提醒。重置按钮不触发

3.10 stay — 在住会话机制 + 订单退款 ⭐⭐⭐⭐(已完成)

背景:系统与亚朵PMS不互通,房间入住/退房由前台在本系统手动操作。核心诉求:入住到退房期间房间客人不变;移动端订单/购物车与当前住客绑定;退房后新客人扫码进来订单和购物车必须是全新的。 方案:引入“在住会话(hotel_room_stay)”——入住=开新会话,退房=关闭会话;订单挂载到会话(stay_id);H5 按会话隔离订单可见性与购物车。

后端(已完成)

  • 实体:HotelRoomStay.java(roomId/guestName/guestPhone/checkInTime/checkOutTime/status/remark,@TableLogic(value="0", delval="id")
  • Mapper:HotelRoomStayMapper.java + XML(selectActiveByRoomId/selectStayPage/selectAllActive,均显式 AND del_flag = 0
  • VO:HotelRoomStayVO.javaRoomCheckOutVO.java(needConfirm/message/activeOrders/checkedOut)
  • Service:HotelRoomStayService(Impl).java — roomCheckIn/roomCheckOut(两步交互)/roomCleaningDone/roomMaintenanceToggle/stayPage/backfillGuest
  • Controller:HotelQrCodeController 新增 5 接口(/room/checkIn、/room/checkOut?force、/room/cleaningDone、/room/maintenanceToggle、/room/stayPage)
  • 房间列表带在住客人:HotelRoomMapper.xml JoinActiveStay + HotelRoomVO 增 stayId/guestName/guestPhone/checkInTime
  • 扫码增强:QrCodeScanVO.RoomInfo 增 roomStatus/stayId(H5 拦截与隔离依据)
  • 房间删除保护:在住中房间禁止删除;roomUpdate 禁止直接改状态(状态仅通过操作流转)
  • 订单绑定会话:orderCreate 写 stay_id + 未入住拦截 + 首单联系人仅空字段回写会话 + 房号以数据库为准;分页支持 stayId 过滤
  • 退房联动:cancelPendingByStayId 批量取消待支付订单(状态 11),进行中订单需二次确认(force)
  • 退款:HotelPayService.refundOrder(全额退款,AlipayTradeRefundRequest,失败标记可重试);4 个自动触发点(拒单/顾客退单/前台退单/审核通过);手动退款接口 POST /hotel/order/refund
  • 回调竞态防护:已取消订单(7/9/10/11)收到支付回调不复活,标记异常流水,由前台手动退款
  • 常量:HotelOrderConstants 新增 11=退房联动已取消 + 退款状态 0/1/2;HotelQrConstants 新增 STAY_ACTIVE/STAY_CHECKED_OUT

前端 PC(已完成)

  • board.vue(房态看板):房间卡片网格 + 状态 Tab + 房间号/房型/楼层筛选 + 入住登记弹窗(住客姓名/电话/备注)+ 退房二次确认弹窗(进行中订单清单)+ 历史入住记录弹窗;菜单由 V1.0.107 写入
  • room.vue:回归为 AiCrudPage 纯 CRUD(房间号/房型/楼层/状态列/排序/备注);在住业务操作已拆到看板页,编辑表单不含状态字段
  • order.vue:退款状态列(DictTag hotel_refund_status)+ 手动退款按钮(已支付未退款且已取消/退款失败场景)
  • api/hotel.js:新增 6 个接口(roomCheckIn/roomCheckOut/roomCleaningDone/roomMaintenanceToggle/getRoomStayPage/orderRefund)
  • ⚠️ board.vueroom.vue 的房间状态标签/颜色为前端硬编码映射(statusLabelMap/statusColorMap),与 AGENTS.md 5.7「字典禁止硬编码」不一致,待补 hotel_room_status 字典后改 DictTag

前端 H5(已完成)

  • store/hotel-order.js:roomInfo 增 stayId/roomStatus;setRoomInfo 检测会话变更自动清空购物车(退房后新客人全新购物车)
  • room-confirm.vue:非入住中房间拦截点餐(“该房间暂未入住,如需点餐请联系前台”)
  • orders.vue:订单列表改为按 stayId 过滤(同会话共享可见,退房后隔离),无会话时退回按房间号兼容存量数据;状态映射补 11=已取消
  • order-status.vue:状态 11 文案/图标/提示补齐(“房间已退房,该订单已自动取消”)
  • order-confirm.vue:下单已传 roomId,后端负责会话查找与拦截(双重保险)

房间状态流转规则(仅通过操作流转,禁止编辑表单直接改)

空闲/打扫中 --入住--> 入住中 --退房--> 打扫中 --打扫完成--> 空闲;空闲 <--> 维护中。
退房时:待支付订单自动取消(11);进行中订单(2/3/4/5/8)需前台二次确认后强制退房。

技术决策(本模块)

  • 入住不采集客人信息,首单联系人仅空字段回写会话(多人同住以第一位下单客人作为在住客人)
  • 退房两步交互:后端返回 needConfirm + 订单清单,前端弹窗确认后 force=true 重调,避免误操作丢失进行中订单
  • 退款仅全额退款;退款失败不阻断订单状态流转(内部持久化 refund_status=2),PC 订单页可手动重试
  • 退房竞态(退房瞬间顾客支付成功):回调不复活已取消订单,流水标记异常,由前台在订单页手动退款

四、框架适配速查表

适配项 jeeplus(源) Forge(目标)
实体基类 BaseEntity TenantEntity(含 tenantId + 审计字段)
ID 生成 UUID.randomUUID() @TableId(type = IdType.ASSIGN_ID) 雪花算法
响应体 ResponseEntity / ResponseUtil RespInfo.success(data) / RespInfo.error(msg)
注入方式 @Resource @Autowired 字段注入(实地统计:forge-hotel 共 57 处 @Autowired、0 处 @RequiredArgsConstructor
权限注解 @PreAuthorize("hasAuthority('xxx')") @SaCheckPermission("xxx")
日志注解 @ApiLog @OperationLog
SQL 位置 Service 层 QueryWrapper Mapper XML(DataScopeInterceptor 要求)
逻辑删除 手动 setDelFlag(1) + updateById @TableLogic(value="0", delval="id") 自动
租户隔离 TenantLineInnerInterceptor 自动追加 tenant_id
字典 硬编码状态值 DictSelect / DictTag / useDict()
前端列表 手写 NInput/NSelect AiCrudPage 配置化
分页参数 Page<T> pageNum + pageSize@RequestParam
分布式锁 synchronized Redisson(@Idempotent 或手动)

五、建议开发顺序

✅ 1. room(房间/房型管理)     ← 已完成
✅ 2. qrcode(二维码管理)      ← 已完成
✅ 3. open(开放扫码接口)      ← 已完成
✅ 4. dish-spec(菜品规格管理) ← 已完成
✅ 5. business-hours(营业时段)← 已完成
⏸️ 6. hotelconfig(酒店配置)   ← 暂缓(配送费/配送时长待讨论)
 7. dish(加料/日志)         ← 待开发
✅ 8. order(订单管理)         ← 全部完成(后端+PC管理端+H5顾客端+支付系统)
✅ 9. notification(实时通知)  ← 已完成(SSE + 声音提醒 + 桌面通知)
✅ 9.1 stay(在住会话+退款)   ← 全部完成(入住/退房/打扫/维护 + 退房联动 + 自动/手动退款)

六、数据库表汇总

已完成(Forge 已有)

表名 说明
hotel_qr_code 二维码表
hotel_qr_bind_log 二维码绑定日志表
hotel_room 房间表
hotel_room_type 房型表

已完成(order 模块)

表名 说明 迁移脚本
hotel_order 订单主表(23+字段 + 4索引,V1.0.105 新增 payStatus/payExpireTime/paySource) V1.0.104 + V1.0.105
hotel_order_item 订单明细表(含 spec_option_ids / addition_ids 作为服务端重算加价的依据) V1.0.104(建表)+ V1.0.109(增两列 ID)

已完成(pay 模块)

表名 说明 迁移脚本
hotel_pay_log 支付流水表(outTradeNo/tradeNo/payAmount/payStatus/payTime/notifyContent) V1.0.106
hotel_room_stay 房间在住会话表(roomId/guestName/guestPhone/checkInTime/checkOutTime/status 0=在住 1=已退房)

V1.0.107__add_hotel_room_board_menu.sql:无建表,仅新增「房态看板」菜单(/hotel/boardhotel/board,sort=9,动态查找 酒店管理 父菜单 + NOT EXISTS 防重)

hotel_order 扩展字段(V1.0.106)stay_id(在住会话ID + 索引)、refund_amount(实际退款金额)、refund_trade_no(支付宝退款交易号)、refund_status(0=未退款/1=成功/2=失败可重试)

待创建

表名 说明 所属模块
hotel_config 酒店基础配置 hotelconfig(暂缓)

已完成(dish + dish-spec + business-hours 模块)

表名 说明 迁移脚本
hotel_dish 菜品(含主图/附加图片/主厨推荐/售罄/上下架) V1.0.101
hotel_dish_category 菜品分类 V1.0.101
hotel_dish_spec_group 菜品规格组 V1.0.101(建表)+ V1.0.102(菜单)
hotel_dish_spec_option 规格选项 V1.0.101(建表)
hotel_dish_addition 加料 V1.0.101
hotel_dish_log 菜品操作日志 V1.0.101
hotel_business_hours 营业时段 V1.0.103(建表 + 菜单 + 字典)

已完成字典

字典类型 说明 迁移脚本
hotel_dish_status 菜品状态(在售/下架) V1.0.101
hotel_dish_sold_out 售罄状态(正常/售罄) V1.0.101
hotel_meal_type 餐段类型(早餐/午餐/晚餐) V1.0.103
hotel_business_hours_status 营业时段状态(启用/禁用) V1.0.103

已完成字典(order 模块)

字典类型 说明 迁移脚本
hotel_order_status 订单状态(0~11,含待支付/超时取消/11=退房联动已取消) V1.0.104 + V1.0.105 + V1.0.106
hotel_pay_method 支付方式(MOCK/CASH/ONLINE/CREDIT) V1.0.104
hotel_delivery_time_type 配送时间类型(ASAP/CUSTOM) V1.0.104
hotel_room_stay_status 在住会话状态(0=在住/1=已退房) V1.0.106
hotel_refund_status 订单退款状态(0=未退款/1=退款成功/2=退款失败可重试) V1.0.106

待新增字典

字典类型 说明 所属模块
(暂无)

七、前端页面汇总

已完成

页面 路径 组件 说明
二维码管理 views/hotel/qrcode.vue AiCrudPage 批量生成/绑定/解绑/批量下载 ZIP
房态看板 views/hotel/board.vue 自定义卡片网格 房间状态 Tab + 筛选 + 入住/退房/打扫/维护 + 退房二次确认 + 入住记录弹窗
房间管理 views/hotel/room.vue AiCrudPage 房间纯 CRUD(业务操作已拆到房态看板)
房型管理 views/hotel/roomType.vue AiCrudPage 房型 CRUD
菜品管理 views/hotel/dish.vue AiCrudPage 图片预览/上下架/售罄/主厨推荐
菜品分类 views/hotel/dishCategory.vue AiCrudPage 分类 CRUD + 启禁用
菜品规格 views/hotel/dishSpec.vue AiCrudPage 规格组列表 + 选项管理弹窗
营业时段 views/hotel/businessHours.vue AiCrudPage 时段列表 + 时间段下拉选择 + 启禁用
扫码绑定 (H5) forge-h5-ui/src/pages/hotel/scan-bind.vue UniApp 【员工 H5】双职责页authStore.isLogin 分流 —— 未登录转顾客 room-confirm,已登录展示绑定/解绑。小程序化时 #ifdef H5 排除
扫码页 (H5) forge-h5-ui/src/pages/hotel/qr-scanner.vue UniApp 【员工 H5】html5-qrcode 相机扫码(依赖 DOM + getUserMedia,需 HTTPS 安全上下文)。小程序化时 #ifdef H5 排除

已完成(order 模块 — PC 管理端)

页面 路径 组件 说明
订单管理 views/hotel/order.vue AiCrudPage 看板统计 + 流程操作 + 详情弹窗 + 退单审核 + 退款状态列 + 手动退款

已完成(order 模块 — H5 顾客端)

页面 路径 Tab 说明
房号确认 forge-h5-ui/src/pages/hotel/customer/room-confirm.vue 入口 扫码验证房间、非入住拦截、房间卡片、开始点餐
点餐菜单 forge-h5-ui/src/pages/hotel/customer/menu.vue Tab1 分类导航、菜品列表、搜索、购物车浮层
菜品详情 forge-h5-ui/src/pages/hotel/customer/dish-detail.vue 子页 大图、规格选择、加料、备注、加入购物车
购物车 forge-h5-ui/src/pages/hotel/customer/cart.vue Tab3 商品列表、数量控制、备注、费用汇总、结算
确认下单 forge-h5-ui/src/pages/hotel/customer/order-confirm.vue 子页 配送信息、送达时间、明细、提交订单(已移除硬编码支付方式)
支付确认 forge-h5-ui/src/pages/hotel/customer/pay.vue 子页 按后端 payChannel 分流 + 支付宝 H5 WAP Pay + 轮询 + 主动查询兜底 + 倒计时。✅ Mock 免付款兜底已拆为独立分支 + 显式报错(2026-09-07);🔴 MP-ALIPAY 分支支付仍不可用
订单状态 forge-h5-ui/src/pages/hotel/customer/order-status.vue Tab2 进度条、状态卡片、10秒轮询、自定义退单弹窗
历史订单 forge-h5-ui/src/pages/hotel/customer/orders.vue Tab2 订单卡片列表(按在住会话 stayId 过滤)、分页加载、状态标签、待支付"去支付"按钮

待开发

页面 路径 说明 源框架参考
酒店配置 views/hotel/config.vue 单例配置表单(暂缓) HotelConfigManagement.vue
厨房待备餐大屏 待定 待备餐列表 + 出餐 + 语音播报 + 小票打印(需求 4.3,未开发) KitchenOrders.vue
数据看板 待定 热门菜品/平均送达时长/趋势图(需求 4.5,现有 5 指标集成在 order.vue) 无直接参考
订单看板 views/hotel/orderDashboard.vue 已集成到 order.vue 工具栏,无需单独页面 无直接参考

八、关键决策记录

8.1 暂缓开发项

模块 原因 待讨论点
hotelconfig(酒店基础配置) 暂不需要 配送费规则(按距离/区域/固定?)、预计配送时长(固定/动态?)

8.2 技术决策

决策 说明
规格组统一命名 源框架同时存在 hotel_dish_spechotel_dish_spec_group,Forge 统一使用 hotel_dish_spec_group
营业时段时间选择 使用下拉选择(09:00~23:30,每 30 分钟一档),而非时间选择器
餐段类型 仅保留早餐/午餐/晚餐三种,移除夜宵
前端组件规范 统一使用 AiCrudPage 配置化页面,保持分页/筛选/操作列格式一致
订单状态值 使用数字 0~11 连续编号(0=待支付, 1~9 原有状态, 10=超时取消, 11=退房联动取消),非英文字符串常量
订单金额 使用 BigDecimal + 元(DECIMAL(10,2)),与 dish 模块保持一致
订单模块包结构 独立 order/ 子包,与已有 hotel 代码分离
看板统计集成 订单看板统计集成到 AiCrudPage 工具栏 #toolbar-start slot,不单独建页面
H5 顾客端框架 Vue 3 <script setup> + Pinia + uni-app,从旧框架 Vue 2 Options API + Vuex 迁移
H5 设计风格 亚朵酒店深墨绿主题(#2D5016),暖灰背景 #F7F7F7,充足留白,自然温暖
H5 TabBar 架构 自定义 HotelTabBar 组件 + uni.reLaunch 导航,3-tab(点餐/订单/购物车),移除"我的"避免与系统 tabBar 页面冲突
H5 支付页金额 支付页通过 api.hotelOrderDetail() 从后端获取真实金额,不依赖前端 URL 参数(购物车清空后 URL 参数失效)
H5 退单弹窗 自定义弹窗替代 uni.showModal,包含标题/提示/原因输入框/取消确定按钮,样式统一
H5 购物车 Store Pinia + pinia-plugin-persistedstate 持久化,specKey 合并相同规格商品
H5 订单轮询 order-status.vue 对未完成订单每 10 秒轮询刷新,onUnmounted 清理定时器
H5 后端对接 订单状态使用数字 0~11;后端按 dishId 从数据库重算 unitPrice,不信任前端价格。✅ 重算已计入规格加价 price_extra 与加料加价 extra_price(2026-09-04,hotel_order_itemspec_option_ids/addition_ids,迁移脚本为 V1.0.109;重算位置 HotelOrderServiceImpl.orderCreate() L198-216);配送费同步改为服务端读 hotel_config.delivery_fee(V1.0.112)—— 验收结论详见 酒店模块需求缺口清单.md 3.1
支付宝 H5 WAP Pay 使用 AlipayTradeWapPayRequest + pageExecute() 返回 HTML 表单,前端渲染后自动提交跳转支付宝收银台。⚠️ 仅适用 H5pageExecute() 本地签名即成功、不调网关、无 tradeNo;小程序必须另走 AlipayTradeCreateRequestalipay.trade.create)+ my.tradePay({tradeNO}),两条路径不得复用
支付回调租户上下文 支付宝回调无登录态/租户上下文,需从 payLog 提取 tenantId 后用 TenantContextHolder.executeWithTenant() 包裹后续 DB 操作
支付回调幂等 UPDATE WHERE pay_status != 1 利用数据库行锁保证原子性,替代 Java 层 if 检查
支付回调异常处理 catch 块返回 "fail" 让支付宝自动重试(之前返回 "success" 导致异常时支付宝不重试)
支付轮询机制 递归 setTimeout(非 setInterval),首次延迟 3 秒给回调留处理时间,超时后主动查支付宝兜底
vConsole 调试工具 开发阶段通过 CDN + 环境变量开启,联调完成后从 index.htmlApp.vue.env.production 三处彻底移除
H5 按钮越界修复 width: 100% + padding 组合需配合 box-sizing: border-box 防止溢出(cart/order-confirm/pay 三个页面)
在住会话隔离 入住=开新会话、退房=关会话,订单挂 stay_id;H5 订单按 stayId 过滤、购物车随 stayId 变更自动清空,实现退房后新客人全新体验
房间状态只走操作流转 编辑表单/接口禁止直接改房间状态,仅通过入住/退房/打扫/维护接口流转;在住中房间禁止删除
退房两步交互 存在进行中订单时后端返回 needConfirm + 订单清单,前台确认后 force=true 强制退房;待支付订单直接自动取消(11)
退款策略 仅全额退款;拒单/顾客退单/前台退单/审核通过 4 个触发点自动退;退款失败标记可重试不阻断流转;退房竞态异常单由前台手动退款
支付回调竞态 已取消订单(7/9/10/11)收到回调不复活,流水标记 [竞态异常-订单已取消],人工介入退款
下单房号以数据库为准 orderCreate 不信任前端传的 roomNo,按 roomId 查房间实体回填,避免旧缓存/二维码改绑串单
房态看板与房间管理拆分 房间业务操作(入住/退房/打扫/维护/入住记录)独立为 board.vueroom.vue 回归 AiCrudPage 纯 CRUD;菜单由 V1.0.107 写入(sort=9,动态查父菜单 + NOT EXISTS 防重)
支付超时取消用定时任务 PayTimeoutTask@Scheduled(fixedDelay=120000, initialDelay=30000) 轮询,配 TenantContextHolder.executeIgnore 跨租户扫描 selectExpiredUnpaidOrderIdscancelTimeoutOrder(状态 10),不引入延迟消息中间件
放弃待支付订单物理删除 abandonOrder 对未支付订单做物理删除(订单 + 明细),依据 AGENTS.md 5.11「无恢复/审计要求的中间态」例外;已支付订单一律走逻辑删除 + 退款
支付宝授权接口 /hotel/open/alipay-auth/getUserInfo(authCode → 姓名,加密响应 → 手机号)。✅ 已被 room-confirm.vue L376-396 调用(纠正旧记录「当前 H5 未调用」),已实现授权成功/取消/失败三分支。小程序化时作为 buyer_id / user_id → stayId 绑定的数据来源;✅ 手机号日志已脱敏(2026-09-07,AGENTS.md 5.9)
跨服务 SSE 通知转发 8583 AppServer 与 8580 AdminServer 双 JVM 内存不共享,支付成功后由 AppServer HTTP POST 调 AdminServer /hotel/notification/remote/notifyNewOrder,以 server.type 配置区分服务角色
开放接口鉴权现状 已加固(2026-09-04):SQL 层显式 tenant_id 过滤 + 抽出 requireTenantId() 统一校验 10 个开放接口 + DEBUG/INFO/WARN 三级审计日志。⏳ R2 仍开放requireOrder() 只校验租户、不校验订单归属(stayId)—— 已确认随小程序化自然消解(平台 user_id/openid 不可伪造 → 后端按映射校验,禁止前端直传 stayId)。签名 token 方案已废弃。详见 酒店模块需求缺口清单.md 3.2
文档职责拆分 本文档只承载「代码地图与实现进度」;需求验收对照、P0/P1 缺口台账、补齐顺序一律进 酒店模块需求缺口清单.md,缺口关闭后细节转入 code-copilot/changes/<变更名>/
营业时间校验接入顾客端 后端新增 BusinessHoursStatusVO + currentStatus() 推导 + GET /hotel/open/customer/businessHours 免登录接口;H5 三页接入(room-confirm 入口整页拦截 / menu 菜品区独立校验 / order-confirm 提交阻断)+ 后端 orderCreate() 兜底校验;删除 room-confirm.vue 硬编码 '10:00 - 22:00'。变更目录:code-copilot/changes/hotel-customer-business-hours-check/
顾客端载体与首发范围(2026-09-07) 正式载体为微信小程序 + 支付宝小程序(双端),H5 仅用于开发阶段测试。首发只做支付宝小程序,微信支付未开通期间微信小程序暂缓上架(后端保留 #ifdef MP-WEIXIN 占位代码 + WECHAT 渠道显式阻断,三端共用一套后端)
员工端载体分离(2026-09-07) 员工扫码绑定房间确认为员工登录 H5 后操作,不属于小程序,功能已完成不需改造。scan-bind.vue + qr-scanner.vue#ifdef H5 整页排除,其独占的 AiIcon/AiButton/AiResult/html5-qrcode 自动不进小程序包。顾客小程序扫码入口页是 customer/room-confirm.vue
二维码方案:自研普通链接码(2026-09-07) 不调用 wxacode.getUnlimited / alipay.open.app.qrcode.create。同一个 https URL 在微信与支付宝后台各配一条前缀规则,两平台规则表相互独立 → 天然实现双端自动识别。两套 URL 必须分开:员工 H5 用 /h5/(不配规则)、顾客码用 /r/(配规则)。hash 路由的 path 恒为 /,直接配 https://域名/ 会劫持员工 H5。参数名 c/t/s 与 query 形式不得更改(保证员工 parseQrUrl 零改动)
二维码改造零迁移成本(2026-09-07) hotel_qr_code 表只存 short_code + signqr_url/qr_content 字段,URL 由 QrCodeUtils.buildQrCodeUrl() 运行时拼接 → 改格式只需把硬编码 HotelQrConstants.QR_BASE_PATH 改为配置项 hotel.qr.path,不涉及数据迁移、不影响已生成的码。但桌牌印制后改动 = 全部重印,故列入第 2 批(资质到位前完成)。✅ 已落地(2026-09-07):四参重载 + 三层回退(yml 显式配置 → @Value 空串默认值 → QR_BASE_PATH),dev 保持旧 hash 值、生产 ${FORGE_HOTEL_QR_PATH:/r/},既有码 URL 逐字符相同
排斥非微信/支付宝扫码需两层(2026-09-07) 入口层:平台扫一扫命中已配规则时不向服务器发 HTTP 请求,其它扫码器会真实请求 /r//r/ 对所有真实 HTTP 请求返回 403(仅放行平台校验用 .txt 文件,不放提示页);接口层:顾客端开放接口要求平台身份(user_id/openid)。只做第一层是假的,403 挡不住直接 curl 调接口,只有接口层是真正的安全边界。实现口径以 酒店二维码模块开发文档.md 4.6 为准
小程序化手法约束:不影响 H5 测试(2026-09-07) 所有改动必须归入三类之一,禁止直接替换现有实现:① 条件编译(#ifdef H5 保留原实现)② 配置开关(application.yml 默认严格、application-dev.yml 覆盖宽松)③ 纯增量(新增接口/分支/文件)。pages.json 启动页 pages/login/index 不改,小程序侧首页用条件编译换成 room-confirm
顾客端依赖面已核实收敛(2026-09-07) 顾客 8 页全部外部依赖仅:HotelTabBar.vue + @/api + getFileDownloadUrl + hotel-order store。顾客 8 页不用 AiIcon、不用 CSS mask、不用 v-loading,图标全为 emoji + 原生 <image> → 页面级零改造;唯一待改组件是 HotelTabBar.vue L44-51(WebkitMask + 本地 SVG,只 3 个图标)
Mock 支付开关化(2026-09-07,✅ 已落地) 新增 hotel.pay.mock-enabledapplication.yml 默认 ${FORGE_HOTEL_PAY_MOCK_ENABLED:false}application-dev.yml 覆盖 true)。实际实现与原方案的三处差异:① createPay 显式拒 MOCK 被 mockOrReject 下沉取代(开关只在一处读 + 保留差异化报错文案);② pay.vue else 采「拆分」而非「删除」(新增 payChannel === 'MOCK' 独立分支保住 H5 调试);③ 未引入 wechatPayProperties(微信支付完全未实现,新增永远 false 的配置项只多一个误配风险面)。建议变更名 hotel-pay-mock-hardening,🔴 涉及资金必须人工审查(AGENTS.md 5.9),目前 Spec 未补、审查未做
支付渠道单一收口点(2026-09-07) 所有非支付宝分支统一走 mockOrReject(reason)全仓禁止裸 return "MOCK"(改造前有两处静默降级,正是免付款漏洞成因)。ALIPAYALIPAY_MP 归为同一 payChannel,因此涉及 trade.createwap.pay 区分时必须用原始 paySource,不能用 payChannel
退款渠道显式判定(2026-09-07,防资损) 禁用「非 ALIPAY 即 MOCK」兜底:paySource=MOCK 或流水 pay_channel=MOCK 或无流水 → 仅回写状态;ALIPAYalipay.trade.refund;其余 → 抛「请联系前台人工处理」。否则未来接入微信后,微信订单退款会被误判为无资金流而直接标记成功,造成真实资损
沙箱期验证方式(2026-09-07) 支付宝沙箱 App 的扫一扫不是通用扫码器,只识别沙箱应用内生成的特定格式码,普通 URL 二维码不解析 → 沙箱期无法真机验证扫码跳转。只能用支付宝小程序开发者工具「编译模式 → 模拟扫码」注入 options.qrCode(微信侧对应「通过二维码编译」);s 签名必须用后端 QrCodeUtils.generateSign() 真实算出(dev 密钥 hotel_qr_default_secret),否则 /hotel/open/scan 验签拒绝。沙箱支付同样只能在支付宝 IDE 内验证

*文档最后更新:2026-09-07(小程序化决策入档:① 纠正支付相关错误记录 —— 支付宝小程序支付100% 不可用而非免付款(payForm 有值但 tradeNoundefined),支付环境「自动识别」实为前端判断;② 登记 Mock 免登录资金红线(mockSuccess 无开关 + paySource 默认 MOCK);③ 纠正「支付宝授权接口 H5 未调用」—— room-confirm.vue L376-396 已接入;④ 补记 scan-bind.vue 双职责(员工/顾客分流)与员工端载体分离结论;⑤ 同步已修复项(金额重算、配送费、开放接口加固);⑥ 新增二维码自研普通链接码方案、两层拦截、零迁移成本、H5 零影响手法约束、依赖面收敛、沙箱验证方式六项决策。

2026-09-07 四份文档去重归位 + 纠错:① 框架适配速查表的「注入方式」原写 @RequiredArgsConstructor + private final,与代码完全相反,实地统计为 57 处 @Autowired、0 处 @RequiredArgsConstructor,已更正;② hotel_order_item 增列的迁移版本原记 V1.0.104实际为 V1.0.109(V1.0.104 是建表,V1.0.108 是 hotel_business_hours.day_of_week 改类型,与本项无关),已更正;③ /r/ 入口层原写「静态提示页」,与二维码文档 4.6 的「403」矛盾,已统一为 403

2026-09-07 第 0 批(资金红线收口)+ 第 2 批(二维码 URL 配置化)落地回写:① 支付子包代码地图补 HotelPayConfig.java(原文档未登记)、resolvePayChannel/mockOrReject 单一收口、refundOrder 渠道显式判定;② 总览表 8.1 pay 与 8.3 alipay-auth 两行状态从 🔴/⚠️ 改为 ✅,2 qrcode 行补 URL 配置化;③ 纠正三处文档失真:环境变量名实为 FORGE_HOTEL_PAY_MOCK_ENABLED(非 FORGE_HOTEL_PAY_MOCK)、pay.vue 分流依据实为 createData.payChannel(非 paySource)、resolveScanParams 实返 {shortCode, sign}(非 {c,t,s});④ 补登 pages.json L67-87 已裁员工两页、HotelTabBar.vue 已拆双分支、room-confirm.vue resolveScanParams 已落地;⑤ 登记三项实测结论:<Teleport> 不支持小程序(AiAvatarCropper.vue 导致 build:mp-alipay 2.4s 失败,页面裁剪挡不住)、小程序 <image> 无法解析 stroke="currentColor".env.mp-alipay 必须靠 --mode mp-alipay 才会被加载(uni-app 不按平台加载 env);⑥ 登记 pages/index/index.vue 的 5 处 #ifdef H5manifest.jsonmp-alipay.appid 占位、package.json--mode mp-alipay 三项均在位git diff 核实),均不影响 H5 dev;⑥-1 🔴 纠错:本节与 H5 基础设施段原记「三处已被用户回退」「.env.mp-alipay 为死配置」,系仅凭 IDE attached_files 提示推断、未用 git diff 核实所致,经复核全部错误,已按事实更正(后续判断工作区状态一律以 git diff 为准);⑦ 新登记一项 5.9 红线:application-dev.yml 未被 gitignore 且含支付宝沙箱密钥明文;⑧ 全文标注 AGENTS.md 5.9 合规未闭环(代码先行、Spec 未补、人工审查未做)。

本文档只承载「代码地图与实现进度」。四份酒店文档均在 forge-server/forge-business/forge-hotel/ 同一目录:需求视角的验收对照与批次排期见 酒店模块需求缺口清单.md;二维码实现细节见 酒店二维码模块开发文档.md;支付实现细节见 酒店订单支付系统开发文档.md(2026-09-07 已用 git mv 从仓库根目录移入本目录)。已废弃的 酒店AGENTS.mdforge-h5-ui/AGENTS.md 已删除)*