# 营业时间校验接入顾客端 > status: proposed > created: 2026-09-03 > complexity: 🟡中等 > 需求依据:`forge-server/forge-business/forge-hotel/需求说明书.html` v1.0 → 4.1 顾客端「营业时间提示」(P1) > 缺口台账:`forge-server/forge-business/forge-hotel/酒店模块需求缺口清单.md` 5.1 ## 1. 背景与目标 ### 1.1 现状缺陷 后端营业时段能力**已完备但顾客端完全未接入**,且 H5 当前向顾客展示的是**硬编码的假营业时间**: | 环节 | 位置 | 现状 | |------|------|------| | 后端判断 | `HotelBusinessHoursServiceImpl.checkCurrentBusinessHours()` L49-57 | ✅ 已实现,按 `dayOfWeek` + `HH:mm` 字符串区间匹配 | | 后端接口 | `GET /hotel/business-hours/check-current` L62-66 | ✅ 已存在,但**需登录** | | PC 封装 | `forge-admin-ui/src/api/hotel.js` L305-307 `checkCurrentBusinessHours()` | ✅ 已封装 | | H5 封装 | `forge-h5-ui/src/api/index.js` | ❌ **无任何营业时段接口** | | H5 展示 | `room-confirm.vue` L107 `businessHoursText = ref('10:00 - 22:00')` | 🔴 **硬编码,且声明后从未被赋值更新**,L62 直接渲染给顾客 | | H5 拦截 | `order-confirm.vue` `submitOrder()` L119-168 | ❌ 无营业时间校验 | | 后端拦截 | `HotelCustomerController.orderCreate()` L120-131 | ❌ 无营业时间校验,非营业时段可正常下单 | **后果**:后台修改营业时段对顾客端**零影响**;顾客在任何时间都能下单,餐厅非营业时段收到订单无人处理。 ### 1.2 鉴权约束(决定方案形态) `GET /hotel/business-hours/check-current` **不在 Sa-Token 白名单内**,顾客端免登录调不通: - `SaTokenConfig` L79 `.notMatch("/hotel/open/**")`、L104 `.excludePathPatterns("/hotel/open/**")` - 白名单只有 `/hotel/open/**`、`/hotel/notification/subscribe`、`/hotel/notification/remote/**` - `HotelBusinessHoursController` 全部端点带 `@OperationLog` 且**无 `@SaIgnore`** 因此必须新增 `/hotel/open/customer/businessHours` 开放接口,而不是给 `check-current` 加 `@SaIgnore`——后者会让**管理端**的营业时段查询接口一并失去鉴权与操作日志。 ### 1.3 目标 - 顾客端展示的营业时间来自数据库真实配置,硬编码清零 - **非营业时段顾客端完全不可点餐**:房号确认页整页拦截、菜单页不展示菜品,顾客无法进入点餐流程 - 明确告知下次营业时段,并提供「重新检查」入口,避免客人到点后被迫重新扫码 - 营业中进入、停留至打烊才提交的场景:提交时二次校验 + 后端异常双重阻断 - 未配置任何启用时段的租户:**fail-open**,显示「全天营业」且不拦截,避免配置缺失把点餐全关死 - 不新增数据库表/字段/字典,不修改 Sa-Token 配置 ## 2. 研究结论 ### 2.1 可复用的既有能力(不新增 SQL) `HotelBusinessHoursMapper.xml` 已有 `selectEnabled`(L29-37,返回该租户全部 `status='ENABLED'` 且 `del_flag=0` 的时段,含所有星期)。计算「当天时段」与「下次营业时段」所需数据**全部可由这一次查询在 Java 层推导**,无需新增 Mapper 方法与 XML,符合 AGENTS.md 5.1「SQL 写在 Mapper XML」且避免 SQL 面扩散。 ### 2.2 时段匹配语义(新代码必须与之一致) `selectCurrentBusinessHours`(L40-52)的判定规则: ```sql AND status = 'ENABLED' AND del_flag = 0 AND (day_of_week = #{dayOfWeek} OR day_of_week IS NULL) AND #{currentTime} >= start_time AND #{currentTime} < end_time ORDER BY sort_order ASC LIMIT 1 ``` - `day_of_week`:`0=周日, 1=周一, …, 6=周六`,**`NULL` 表示每天**(Java 侧取值 `LocalDate.now().getDayOfWeek().getValue() % 7`) - 时间比较是 `HH:mm` **字符串字典序**比较,仅对零填充的 24 小时制成立;**左闭右开**(`>= start`、`< end`) - ⚠️ **已知限制**:跨零点时段(如 `22:00-02:00`)用字符串比较**永不命中**。当前 PC 编辑表单的开始/结束时间限定为 `09:00~23:30` 每 30 分钟一档的下拉,无法配置跨天时段,故该限制**当前不可达**。本变更**不修复**此限制,但 `currentStatus()` 的下次营业推导必须**沿用同一语义**(同样不支持跨零点),避免前后端判断打架。若后续放开跨天配置,须同时改 SQL 与本推导逻辑。 - 时区:沿用现有 `LocalDate.now()` / `LocalTime.now()` 的 JVM 默认时区,本变更不引入时区配置。 ### 2.3 校验落点选择:开放接口 Controller 营业时间校验放在 `HotelCustomerController.orderCreate()`,**不放进** `HotelOrderServiceImpl.orderCreate()`。实地核对后的依据: | 事实 | 证据 | |------|------| | PC 管理端**无代客下单入口** | `views/hotel/order.vue` L11 `:hide-add="true"`,`api-config`(L5-6)只有 `list`,无 `add` | | `POST /hotel/order` 是**无调用方的遗留端点** | `HotelOrderController.orderCreate()` L66-73 注释写「客户端下单」,但客户端实际走 `/hotel/open/customer/orderCreate`,PC 又 `hide-add`,全仓无调用方 | | 开放接口是**唯一真实下单入口** | 在此校验即可覆盖全部实际业务流量 | 结论:在 Service 层加校验当前**零收益**(没有任何其它路径创建订单),却会引入 `HotelOrderServiceImpl` → `HotelBusinessHoursService` 的 Service 间依赖。放在开放接口 Controller 既符合 AGENTS.md 5.6「跨 Service 协调逻辑上提到 Controller 层」,又保持零依赖。 > 若未来新增「前台代客下单」入口,需先明确该入口是否受营业时间约束:受约束则把校验下沉到 Service;不受约束(电话订餐等运营例外)则维持现状。这是一次**可逆决策**,不是单向门。 ### 2.4 拦截形态:入口整页拦截(fail fast) **已与业务方确认**:非营业时段顾客**不能浏览菜单**,入口即拦。 | 页面 | 拦截形态 | 复用的既有 UI 模式 | |------|---------|------------------| | `room-confirm.vue` | 整页拦截,**不渲染房间卡片与「开始点餐」按钮**,展示非营业原因 + 下次营业时段 + 「重新检查」 | 现有 `rc-error-page`(L9-38,已用于「二维码未绑定」「房间未入住」) | | `menu.vue` | 保留送达信息栏与分类栏,**菜品列表区替换为非营业占位**,加购入口随之消失 | 现有 `m-empty` 空态(L52-55) | | `order-confirm.vue` | 提交前二次校验,`uni.showModal` 阻断 | — | 选择整页拦截而非「提示条 + 允许浏览」的理由: - **fail fast**:避免客人挑完 5 分钟菜、加满购物车,才在提交时被告知不能下单 - **视觉一致**:与「房间未入住」同为「当前无法点餐」语义,共用一套错误页 UI,客人认知成本为零 - **实现更省**:整页拦截只需一个 `v-else-if` 分支;「允许浏览但禁用加购」需在菜品卡片、快速加减、详情页、购物车四处分别处理禁用态 `menu.vue` **必须独立校验**,不能只靠 `room-confirm` 拦截:`HotelTabBar` 用 `uni.reLaunch` 导航,客人可从 `orders` / `order-status` 页经 TabBar **直达菜单页**,绕过房号确认。 `cart.vue` **不在本变更范围**:非营业时购物车浮层仍可跳转查看已加购商品(营业中加的),结算进入 `order-confirm` 后由 L3 拦截。在 cart 页再加一层拦截收益低且扩大改动面。 **营业状态接口调用失败**(网络异常/5xx)时,`room-confirm` 走整页错误 + 「重新检查」,**不放行**;`menu` 显示占位。UI 层 fail-closed 成本低(客人点一次重试即可),后端仍是最终边界。 > 注意区分两种「不营业」:接口**调用失败** → UI fail-closed(拦住 + 重试);接口**成功但 `configured=false`**(无启用时段配置) → fail-open(全天营业,放行)。 ### 2.5 字典合规判断 本变更**不新增字典**,理由(供 review 核对 AGENTS.md 5.7): - `hotel_meal_type`、`hotel_business_hours_status` 已在 V1.0.103 建好,餐段名 `meal_name` 直接取库中值展示,不做前端映射 - `open` 是布尔判定结果,非业务枚举 - `nextDateText`(今天/明天/周三)属**日期本地化文案**,由后端统一生成,不是可配置业务枚举,不入 `sys_dict_data` ### 2.6 多时段空档期语义(已确认) 配置早餐 `07:00-10:00`、午餐 `11:00-14:00` 时,`10:30` 属**空档期,视为非营业**,提示「午餐 11:00 开始」。 - 与 `selectCurrentBusinessHours` 的 SQL 判定**完全一致**(区间左闭右开、逐段匹配),Java 层不另写一套「当天最早~最晚」的宽松判定,避免两端打架 - 多时段配置的粒度因此有意义;若酒店实际空档期也接单,应在后台**补配一个覆盖空档的时段**,而不是放宽系统判定 ### 2.7 遗留端点权限缺口(本变更不修) 核对中发现 `HotelOrderController` **整个类无任何 `@SaCheckPermission`**,仅有 `@OperationLog`。任何登录用户均可调用接单/拒单/备餐/出餐/配送/完成/退单/审核/**手动退款**共 15 个端点。属独立权限缺口,已记入 `酒店模块需求缺口清单.md`,**不得夹带进本变更**。 ## 3. 功能要求 ### 3.1 后端 - [ ] 新增 `BusinessHoursStatusVO`(`hotel/vo/`),承载营业状态与下次营业信息,内部静态类 `Period` 表达单个时段(沿用 `HotelOrderVO.OrderItemVO` 的内部类先例) - [ ] `HotelBusinessHoursService` 新增 `BusinessHoursStatusVO currentStatus()`,实现类基于 `selectEnabled` 一次查询 + Java 层推导,复用 `checkCurrentBusinessHours()` 判断当前是否营业 - [ ] `currentStatus()` 在**无启用时段**时返回 `configured=false`、`open=true`、`todayText="全天营业"`(fail-open) - [ ] 下次营业推导:先找当天 `startTime > currentTime` 的最早时段;找不到则按 `dayOffset 1..7` 逐日找最早匹配时段 - [ ] `HotelCustomerController` 新增 `GET /hotel/open/customer/businessHours?tenantId=xxx`,用 `TenantContextHolder.executeWithTenant` 包裹,**不加** `@OperationLog`(与该 Controller 其它免登录端点一致) - [ ] `HotelCustomerController.orderCreate()` 在调用 `orderService.orderCreate(dto)` **之前**校验:`configured && !open` 时抛 `BusinessException`,消息含下次营业文案 - [ ] 不修改 `SaTokenConfig`(`/hotel/open/**` 已在白名单) - [ ] 不新增 Flyway 脚本(无表结构与字典变更) ### 3.2 H5 顾客端 **API 与 Store** - [ ] `api/index.js` 新增 `hotelBusinessHours(tenantId)`,置于「酒店顾客端」区块 `hotelAbandonOrder` 之后,带 `skipAuthRefresh: true` - [ ] `store/modules/hotel-order.js` 新增 `businessHours: null` state(存后端 VO 原样)+ `setBusinessHours(v)` action;**不加入** `persist.pick`(L146)—— 营业状态是时效数据,持久化会让下次打开时用旧值误拦或误放;内存态已足够跨 `redirectTo` / `reLaunch` 存活,且每页 `onMounted` 都会重新拉取 - [ ] getter `isOpenNow`:`businessHours` 为 `null`(未拉取)→ `true`(不拦,由各页自己的拉取结果决定);`configured === false` → `true`(全天营业);否则 `open !== false` - [ ] getter `businessHoursText`(当天时段文案,兜底「营业时间以门店公告为准」)、`businessHoursTip`(`nextText`,兜底 `''`) **room-confirm.vue(L1 入口拦截)** - [ ] **删除** L107 硬编码 `const businessHoursText = ref('10:00 - 22:00')`,改为 `computed(() => orderStore.businessHoursText)`;`import` 补 `computed` - [ ] 新增 `const closedInfo = ref(null)`,承载非营业整页拦截数据(`{ nextText }`) - [ ] `loadRoomInfo()` 中在 `orderStore.setTenantId(data.tenantId)`(L171)与 `setRoomInfo`(L172)**之后**、联系人回填(L174)**之前** `await` 拉取营业状态(不再静默失败): - [ ] 成功且 `configured !== false && open === false` → `closedInfo.value = { nextText }`,`loading=false`,**return**(不渲染房间卡片) - [ ] 成功且营业中 → 写入 store,正常渲染,L60-63 营业时间行显示 `businessHoursText` - [ ] 调用失败(网络/5xx)→ `errorMsg.value = '营业状态获取失败,请点击重试'`,`loading=false`,**return**(fail-closed) - [ ] 模板在 `v-else-if="errorMsg"`(L10)与 `v-else`(L41)之间插入 `v-else-if="closedInfo"` 分支,**复用 `rc-error-page` 全套样式类**:标题「当前不在营业时间」,正文展示 `closedInfo.nextText`(为空时只显示「请稍后再试」),按钮文案「重新检查」绑定 `retry()` - [ ] `retry()`(L200-202)需先重置 `closedInfo.value = null` 再调 `loadRoomInfo()` - [ ] `deliveryTime` / `deliveryFee` 硬编码(L108-109、L182-188)**不动**,属缺口清单 5.3 `hotelconfig` 模块 **menu.vue(L2 防绕过)** - [ ] 新增 `const hoursLoading = ref(false)`、`const hoursError = ref(false)`、`const isOpenNow = computed(() => orderStore.isOpenNow)`、`const businessTip = computed(() => orderStore.businessHoursTip)` - [ ] 新增 `loadBusinessHours()`:`orderStore.tenantId` 为空则直接 return;`hoursLoading=true` → 调接口 → 成功写 store、`hoursError=false`;失败 `hoursError=true`;`finally hoursLoading=false` - [ ] `onMounted`(L152-155)追加 `loadBusinessHours()`,与 `loadCategories()` / `loadDishes()` 并列(`reLaunch` 每次重建页面,`onMounted` 已足够,不引入 `onShow`) - [ ] 菜品列表 `scroll-view`(L47-95)内新增分支:`hoursLoading` → 复用 `m-loading`;`!isOpenNow || hoursError` → **非营业占位**(复用 `m-empty` 样式,图标改 `🕐`,文案「当前不在营业时间」+ `businessTip`;`hoursError` 时改为「营业状态获取失败」+ 重试按钮);否则渲染原菜品列表 - [ ] 送达信息栏(L4-8)、分类栏、购物车浮层、`HotelTabBar` **保持渲染**,不改动 - [ ] 菜品卡片、快速加减、`goToDetail`、`cart.vue` **不改动**(非营业时列表不渲染,加购入口自然消失) **order-confirm.vue(L3 提交阻断)** - [ ] `submitOrder()`(L119-168)在手机号校验(L124-127)之后、`if (submitting.value) return`(L128)之前插入:刷新营业状态(`try/catch`,失败仅 `console.log` **不 return**)→ 若 `configured !== false && open === false` 则 `uni.showModal({ title: '暂不可下单', content: nextText ? '当前不在营业时间,' + nextText : '当前不在营业时间,请稍后再试', showCancel: false })` 并 `return`(此时尚未置 `submitting=true`,无需复位) - [ ] 后端 `BusinessException` 消息已由 L163-164 的 `catch` + `uni.showToast(err?.message)` 展示,无需额外分支 - [ ] `orderData` 结构(L132-151)**不改动**——规格/加料 ID 缺失属缺口清单 3.1 资金缺陷,另行提案 ### 3.3 编码风格约束(forge-hotel 模块专属) - [ ] 新增 Java 代码为 JDK 7 风格:**无** lambda `->`、**无** `.stream()`、**无** `::` 方法引用、**无** switch 表达式 `case X ->`、**无** `@PathVariable`、**无** `LambdaQueryWrapper`;排序用 `Collections.sort` + 匿名 `Comparator` - [ ] 依赖注入用 `@Autowired` 字段注入,字段不加 `final`,不使用 `@RequiredArgsConstructor` - [ ] 开放接口的 `tenantId` 用 `@RequestParam` 单独接收(属既有安全上下文参数例外,非查询筛选条件,不违反「查询参数用实体类接收」规范) - [ ] H5 拦截页与非营业占位优先**复用既有样式类**(`rc-error-page` / `rc-error-card` / `m-empty` / `m-loading`),确需新增时只取 `--h-pri` / `--h-acc-lt` 等既有主题变量,不引入新色值 ## 4. 影响范围 **后端(forge-hotel)** - Create: `vo/BusinessHoursStatusVO.java` - Modify: `service/HotelBusinessHoursService.java`、`service/impl/HotelBusinessHoursServiceImpl.java` - Modify: `controller/open/HotelCustomerController.java` **前端(forge-h5-ui)** - Modify: `src/api/index.js`、`src/store/modules/hotel-order.js` - Modify: `src/pages/hotel/customer/room-confirm.vue`、`menu.vue`、`order-confirm.vue` **不受影响** - 数据库结构与字典:无变更,无 Flyway 脚本 - `SaTokenConfig`:无变更 - PC 管理端 `businessHours.vue` / `api/hotel.js` / `order.vue`:无变更 - `HotelOrderServiceImpl` 与 `HotelOrderController`:无变更(校验不落 Service 层;后者无 `@SaCheckPermission` 的权限缺口另案处理,见 2.7) - `cart.vue` / `dish-detail.vue`:无变更(非营业时菜品列表不渲染,进不了详情页;购物车仅可查看,结算由 L3 拦截) - 管理端下单入口 `POST /hotel/order`:**不施加**营业时间限制(无调用方,见 2.3) ## 5. 技术决策 | 决策 | 说明 | |------|------| | 新增开放接口而非给 `check-current` 加 `@SaIgnore` | `@SaIgnore` 会让管理端营业时段查询一并失去鉴权与操作日志;新接口挂在已白名单的 `/hotel/open/**` 下,零配置改动 | | **入口整页拦截(fail fast)** | 已与业务方确认「只有营业时间内才可以点餐」且**不给浏览菜单**。避免客人挑完菜才在提交时被打断;与「房间未入住」共用 `rc-error-page` UI,认知成本为零 | | **menu 页必须独立校验** | `HotelTabBar` 用 `uni.reLaunch`,客人可从 `orders` / `order-status` 直达菜单页,绕过 room-confirm | | **空档期视为非营业** | 已确认。与 SQL 逐段匹配语义一致,不在 Java 层另写宽松判定;多时段配置粒度因此有意义 | | 校验落开放接口 Controller | 见 2.3;PC `hide-add`、`POST /hotel/order` 无调用方,开放接口是唯一真实下单入口;Service 层校验零收益却引入 Service 间依赖 | | 无配置时后端 fail-open | 未配置启用时段视为「全天营业」,不拦截。宁可少拦不可错拦——错拦会导致全店无法点餐 | | 接口**调用失败**时 UI fail-closed | 与上一条不矛盾:`configured=false` 是业务上的「不限制」,而调用失败是**状态未知**。入口页拦住 + 「重新检查」成本低,后端仍是最终边界 | | order-confirm 刷新失败不阻断 | 该页客人已通过入口校验,网络抖动不应让其无法下单,以后端 `BusinessException` 为最终边界 | | 营业状态**不持久化** | `persist.pick`(L146)只放需要跨会话恢复的数据(购物车 / 房间 / 联系人)。营业状态逐分钟变化,持久化等于用旧值做拦截判断,属反向优化 | | 下次营业文案后端生成 | `dayOfWeek` 数字与 `NULL=每天` 的映射是服务端知识,前端只渲染 `nextText`,避免两端各写一份星期逻辑 | | 复用 `selectEnabled`,不新增 SQL | 一次查询即可推导当天时段与未来 7 天最近时段,避免 Mapper 面扩散 | | 沿用字符串时间比较语义 | 与既有 `selectCurrentBusinessHours` 保持一致;跨零点限制显式记录为已知问题(见 2.2),不在本变更修复 | ## 6. 验收标准 ### 6.1 功能 - [ ] 租户**未配置**任何启用时段:H5 房号确认页显示「全天营业」,**无拦截**,按钮为「开始点餐」,可正常下单 - [ ] 租户已配置且**当前在时段内**:显示当天真实时段文案(如「早餐 07:00-10:00 · 午餐 11:00-14:00」),**无拦截**,可正常下单与加购 - [ ] 租户已配置且**当前不在时段内**:`room-confirm` **整页拦截** —— 不渲染房间卡片、不渲染「开始点餐」按钮,展示「当前不在营业时间」+ 下次营业文案 + 「重新检查」按钮 - [ ] 非营业时经 TabBar 从 `orders` / `order-status` **直达 `menu`**:菜品列表区显示非营业占位(`🕐` + 下次营业文案),**不渲染任何菜品卡片**,加购与详情入口不可达;送达信息栏 / 分类栏 / TabBar 仍正常渲染 - [ ] 营业状态接口**调用失败**(断网 / 5xx):`room-confirm` 整页错误 + 「重新检查」,`menu` 显示「营业状态获取失败」+ 重试按钮,两端均**不放行** - [ ] 营业中进入 `menu` 并加购,**停留至打烊后**在 `order-confirm` 点提交:弹出阻断弹窗并展示下次营业时段,**不发起**下单请求 - [ ] 点击「重新检查」:`closedInfo` 先被重置,跨入营业时段后页面恢复为正常房间卡片,**无需重新扫码** - [ ] 绕过前端直连 `POST /hotel/open/customer/orderCreate`:返回 `BusinessException`,消息含下次营业时段,订单未落库 - [ ] 后台修改营业时段后,顾客端**重新扫码即生效**,无需重启服务 - [ ] `dayOfWeek IS NULL`(每天)的时段参与当天判定与下次推导 - [ ] 跨天场景:当天剩余时段为空时,下次营业正确落到次日/后续星期的最早时段,`nextDateText` 为「明天」或对应周名 ### 6.2 代码质量 - [ ] `room-confirm.vue` 中 `'10:00 - 22:00'` 硬编码零残留(全仓检索无命中) - [ ] 新增 Java 代码通过 JDK 7 风格核查:无 `->`(日志字符串除外)、无 `.stream()`、无 `::`、无 `case X ->`、无 `@PathVariable`、无 `LambdaQueryWrapper` - [ ] 新增依赖注入均为 `@Autowired` 字段注入 - [ ] `cd forge && mvn clean install -DskipTests` 编译通过 - [ ] `cd forge-h5-ui && pnpm build` 构建通过 - [ ] `git diff --check` 无空白错误 ### 6.3 文档回填 - [ ] `酒店模块需求缺口清单.md` 5.1 状态由 ❌ 改为 ✅,4.1「营业时间提示」行同步,并在第九节补齐顺序中标注已关闭 - [ ] `酒店模块迁移跟踪.md` 3.x 营业时段小节补记新增的开放接口与 VO;模块状态总览表 business-hours 行 H5 列由 `—` 改为 `✅` ## 7. 业务确认结论与遗留待确认项 ### 7.1 已确认(2026-09-03,业务方确认) | # | 事项 | 结论 | |---|------|------| | 1 | 非营业时段是否允许顾客**浏览菜单** | **不允许**。入口整页拦截、菜单页不渲染菜品,详见 2.4 | | 2 | 多时段之间的**空档期**是否算营业 | **不算营业**,视为非营业并提示下一段开始时间,详见 2.6 | | 3 | 是否需要同步实现缺口 5.4「临时暂停接单」 | **本变更不做**。5.4 需新增开关字段与 Flyway,且语义独立(人为暂停 vs 时段外),合并会让 fail-open 判定复杂化;后续单独提案时可复用本变更的拦截页与阻断链路 | ### 7.2 待确认(不阻塞开发,按默认方案执行) | # | 事项 | 默认方案 | |---|------|---------| | 1 | 非营业拦截页是否需要「联系前台」入口 | **不加**。`rc-error-footer` 的既有文案与弹窗(L36、L209-213)是「房间信息有误」语义,直接复用会误导客人;若业务方要求,需另配一套文案而非复用 `handleWrongRoom` | | 2 | 拦截是否需要覆盖 `cart.vue` | **不覆盖**。非营业时菜品列表不渲染,`cart` 仅能查看营业中已加购的商品,结算由 L3 阻断;见 2.4 | | 3 | 「重新检查」是否需要自动轮询(到点自动放行) | **不做**。客人手动点一次即可,自动轮询会在非营业时段产生持续请求;后端仍是最终边界 | ## 8. 执行结论 待 `/apply` 后回填。