Skip to content

支持多场地多时间段预约 + slots 接口补全 + 调度器修复#8

Open
yangl0610 wants to merge 11 commits intoQSCTech:mainfrom
yangl0610:main
Open

支持多场地多时间段预约 + slots 接口补全 + 调度器修复#8
yangl0610 wants to merge 11 commits intoQSCTech:mainfrom
yangl0610:main

Conversation

@yangl0610
Copy link
Copy Markdown

@yangl0610 yangl0610 commented Apr 30, 2026

Summary

  • 多场地多时间段预约ReservationSlotSelection / PlanSlotSelection 每个 slot 携带完整的 campus/venue/start/end 上下文,支持跨场馆多选;ReservationSubmitRequest / ReservationPlanRequest 去除顶层冗余字段
  • /reservations/slots 补全执行上下文:返回字段新增 venue_idvenue_site_idspace_idtime_idtokenweek_start_date,前端可直接构建 ReservationSlotSelection 提交预约;预约窗口未开放时降级返回 template 时间段骨架(available=false),支持计划预约选场
  • 调度器修复ListDueScheduled 扩展查询 failed 状态,使失败计划每小时重试;materializeOne 兼容 failed→submitting 原子切换;新增 MarkExpiredFailed 定期将预约日期已过的 failed 记录标记为 expired,防止无效重试
  • 路由冲突修复:统一预约路由路径参数名为 {roomId}(UUID 类型),消除 Gin 路由树 panic
  • 测试:新增 service 层纯函数单元测试及 repository 集成测试

Test plan

  • go test ./internal/service/... 全部通过
  • 启动服务无 panic,所有路由正常注册
  • GET /reservations/slots?... 预约窗口未开放时返回 template 降级数据
  • 设置 DB_HOSTgo test ./internal/repository/... 集成测试通过
  • 调度器 POST /internal/tasks/reservation-materialize 返回包含 expired 字段

🤖 Generated with Claude Code

yangl0610 and others added 11 commits April 20, 2026 12:56
- ListVenues / ListSlots 对接 TYYS API
- 修复数值类型 ID 解析问题
- OpenAPI 补充可选值文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
背景:同学在约场地时(尤其是提前很久的 plan 模式)往往可以接受多个
时间段、多个场地,甚至多个校区。将这种灵活性直接编码进 API,避免在
预约时强制只选一个场地。

主要变更:

【API / OpenAPI】
- ReservationSlotSelection 新增 campus_name / venue_name / venue_id /
  start_time / end_time,每个 slot 自带完整上下文,支持跨场馆多选。
- ReservationSubmitRequest / PreviewInput 去除顶层冗余的 campus/venue/
  start/end,改为从各 slot 中携带。
- 新增 ReservationPlanSlotSelection,ReservationPlanRequest 用
  plan_slots[] 替代原 preferred_space_ids。
- Preview / Submit / Plan 三个端点改用 roomPublicId(UUID 路径参数),
  符合 public_id 迁移方向。

【Service 层】
- SlotSelection 增加 campus/venue/start/end/venue_id 字段;trySlots
  每次尝试前同步写回 record,DB 始终记录当前正在尝试的场地信息。
- ReservationPreviewInput / ReservationPlanInput 去除冗余顶层字段。
- PlanSlotSelection:完整的计划意图结构(相当于 SlotSelection 减去
  materialize 时才能确定的 time_id / token / week_start)。
- CreatePlan:reserve_open_at 取所有 PlanSlots 中最早的预约窗口时间,
  确保调度器在第一个场馆开窗时即可触发。
- resolvePreferredSlots:按 (campus, venue) 分组查 ListSlots,按
  space_id 过滤,不做时间范围过滤,由 trySlots 逐个尝试。

【Handler 层】
- 补全 4 个缺失的 handler 方法:ListReservationTemplates、
  CreateRoomReservationPlan、TriggerReservationMaterialize、
  TriggerReservation。
- 所有对外接口一律用 public_id(UUID)查询房间和预约记录。
- 新增 RoomService.GetByPublicID stub。

【Model】
- RoomReservation.PreferredSpaceIDs 重命名为 PlanSlots,存储
  []PlanSlotSelection 的 JSON 序列化。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
背景:
- ListDueScheduled 只查 scheduled 状态,导致 failed 的计划永不重试
- 当预约开放时间到达但部分场馆 ListSlots 尚不可用时,计划被标记为
  failed 后无法被调度器再次尝试
- failed 中预约时段已过实际时间的记录会被无限重试,浪费调度资源

主要变更:

【Repository】
- ListDueScheduled 扩展为查询 reservation_status IN ('scheduled','failed'),
  使 failed 状态的计划每小时也能被重试(兜底场馆窗口延迟开放的情况)
- 新增 MarkExpiredFailed:将 failed 且 reservation_date < today 的记录
  标记为 expired,阻止调度器继续无效重试

【Service】
- materializeOne:AtomicTransitionStatus 兼容 scheduled 和 failed 两种
  初始状态,均可原子切换到 submitting
- MaterializeResult 新增 Expired 字段
- MaterializePlans:每次执行后调用 MarkExpiredFailed 清理当日前的过期记录

【API / OpenAPI】
- ReservationMaterializeResult 新增 expired 字段,返回本次清理数量

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
背景:
- Gin 不允许同一路径前缀下出现两个不同名通配符(:roomId vs
  :roomPublicId),导致服务启动 panic
- /reservations/slots 返回的字段缺少提交预约所需的执行上下文
  (venue_site_id/space_id/time_id/token 等),前端无法直接构建
  ReservationSlotSelection
- 预约窗口未开放时 slots 端点直接报错,无法服务计划预约场景

主要变更:

【路由修复】
- RoomPublicIdPath component parameter 的 name 从 roomPublicId 改为
  roomId,三条预约路由路径从 {roomPublicId} 改为 {roomId},消除
  Gin 路由树冲突;handler 参数名同步更新,UUID 查询逻辑不变

【ReservationSlot schema 扩展】
- 新增字段:campus_name / venue_name / venue_id / venue_site_id /
  space_id / time_id / token / week_start_date
- 所有字段均为可选;预约窗口未开放时 token/week_start_date 为空

【ListSlots 逻辑重写】
- Step 1:从 VenueInfo 解析 venueId/venueSiteId 及 resolvedCampus/
  resolvedVenue
- Step 2:尝试调用 TYYS ReservationDayInfo(可能失败)
- Step 3:dayInfo 失败 → 调用 listSlotsFromTemplate 降级,返回
  template 时间段骨架,available=false,供计划预约选场使用
- Step 5:dayInfo 成功 → 每个 slot 携带完整上下文(venue_id/
  venue_site_id/space_id/time_id/token/week_start_date)

【listSlotsFromTemplate】
- 调用 ListTemplates 获取分场(Spaces)和时间段模板(TimeSlots)
- 以 tmpl.Spaces 为分场列表(不依赖 VenueInfo 层是否携带 spaceId)
- 无分场数据时用空占位,保证时间段仍能返回

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- walkSlots 修复:正确解析 TYYS dayInfo 中 spaceId(父对象 id)和 timeId(子对象 key)
- ListTemplates 改为调 ListSlots(tomorrow) 并剥离实时字段,返回扁平 space×timeslot 列表
- ReservationTemplateResponse 用 slots[] 替换原 spaces[]+time_slots[] 两级结构
- SubmitOrPlan:对开放窗口内 slot 依次尝试实时提交,全败降级 CreatePlan
- SubmitRoomReservation handler 改走 SubmitOrPlan
- Preview 对窗口未开放的 slot 跳过 TYYS 调用,直接标 available=false 并说明开放时间
- resolvePreferredSlots 修复:同 space 多时段不再互相覆盖,匹配时加时间段过滤

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant