Files
lab/docs/design.md
2026-09-21 12:51:08 +08:00

13 KiB
Raw Blame History

实验室预约与资源管理平台 - 需求分析与设计说明

1. 需求分析

1.1 用户角色与需求

角色 核心诉求 主要用例
管理员 ADMIN 保证资源台账准确、预约秩序可控 实验室/设备/课程的增删改查;资源状态流转(报修、启用);用户账号管理;审批任何预约;查看全局日历与统计
教师 TEACHER 排课与科研实验,不想被冲突打乱 创建/维护自己的实验课程;按课程一键生成整期预约;审批学生预约;发起多次预约(如每周教研)
学生 STUDENT 快速找到可用资源并预约 查询实验室/设备可用性;发起单次/多次预约;查看与取消自己的预约;签到与完成

1.2 预约场景定义

场景 输入 系统行为 对应类型
单次预约 实验室(可选设备)、一个开始/结束时间 校验冲突后生成 1 条预约;学生进入待审批,教师/管理员直接通过 SINGLE
多次预约 日期区间 + 星期集合 + 每日时段 按周展开为多个场次,写入 1 条系列 + N 条预约;任一场次冲突则整批拒绝 RECURRING
课程绑定预约 课程 ID(可指定设备) 按课程排课规则展开整期场次,绑定课程 ID;仅授课教师或管理员可发起 COURSE

关键业务规则

  1. 冲突判定必须覆盖:同实验室、同设备、同申请人三个维度。
  2. 学生预约需要审批;教师/管理员预约自动通过(仍要过冲突校验)。
  3. 被驳回/取消/完成的预约不再占用资源。
  4. 资源处于「维护中」不可预约;人数不得超过实验室容量。
  5. 单次预约 ≤ 8 小时、最多提前 60 天、不得预约过去时间,且需落在实验室开放时间内。
  6. 不允许跨天预约(结束时间为次日 00:00 除外)。

2. 领域模型

┌────────┐ 1     n ┌────────┐ 1     n ┌────────┐
│  Lab   │─────────│ Device │         │ Course │
└───┬────┘         └────┬───┘         └───┬────┘
    │ 1                 │ n               │ 1
    │                   │                 │
    │ n         ┌───────┴─────────────────┘
┌───┴───────────┴───┐
│    Reservation    │  n ────── 1 ┌──────────────────┐
│  (场次/时间区间)   │            │ ReservationSeries │
└───┬───────────────┘            └──────────────────┘
    │ n
    │ 1
┌───┴────┐
│  User  │
└────────┘

设计要点

  • 「多次预约/课程预约」采用 系列(Series)+ 场次(Reservation) 两层结构: 每次实际占用都是一条独立 reservation 行,冲突检测、审批、状态流转 都只需处理统一的时间区间模型,避免规则分叉。
  • 时间区间统一使用 左闭右开 [startTime, endTime)。
  • reservation.version 为乐观锁字段,防止并发审批互相覆盖。

2.1 数据表

表 关键字段 说明
sys_user username(UK), password(BCrypt), role, enabled 三类角色
lab code(UK), capacity, status, open_time, close_time 开放时间参与冲突校验
device code(UK), lab_id, status 设备必须归属实验室
course code(UK), teacher_id, lab_id, student_count, start/end_date, weekdays, start/end_time 自带排课规则
reservation_series user_id, lab_id, device_id, type, weekdays, 起止日期/时间 重复规则原始记录
reservation lab_id, device_id, course_id, user_id, start/end_time, status, version 冲突检测主体,建立 (lab_id, start_time, end_time) 等索引

3. 状态机模型

3.1 资源状态机(实验室 / 设备共用规则)

stateDiagram-v2
    [*] --> IDLE
    IDLE --> RESERVED: 预约通过
    IDLE --> MAINTENANCE: 报修/维护
    RESERVED --> IN_USE: 开始使用
    RESERVED --> IDLE: 取消/驳回/过期
    IN_USE --> IDLE: 完成使用
    MAINTENANCE --> IDLE: 维护完成
起始 \ 目标 IDLE RESERVED IN_USE MAINTENANCE
IDLE — ✅ ❌(需经 RESERVED) ✅
RESERVED ✅ — ✅ ❌(需先释放)
IN_USE ✅ ❌(需先释放) — ❌
MAINTENANCE ✅ ❌ ❌ —
  • 实现:EnumStateMachine 通用基类 + LabStateMachine / DeviceStateMachine 注册迁移表; 非法迁移抛出业务错误 3003。
  • 资源状态由预约生命周期驱动:ReservationService.syncLabStatus/syncDeviceStatus 根据「是否存在 IN_USE / 占用态预约」推导目标状态,并沿状态机的合法路径推进 (如 IDLE → RESERVED → IN_USE)。
  • 维护中的资源不会被状态同步覆盖,保证报修期间状态稳定。

3.2 预约状态机

stateDiagram-v2
    [*] --> PENDING: 学生提交
    [*] --> APPROVED: 教师/管理员提交
    PENDING --> APPROVED: 审批通过
    PENDING --> REJECTED: 驳回
    PENDING --> CANCELLED: 本人/管理员取消
    APPROVED --> IN_USE: 签到开始
    APPROVED --> CANCELLED: 取消
    APPROVED --> COMPLETED: 到期自动完结
    IN_USE --> COMPLETED: 完成使用
    REJECTED --> [*]
    CANCELLED --> [*]
    COMPLETED --> [*]

4. 预约冲突检测算法

4.1 规则清单

类型 触发条件 错误码
INVALID_TIME_RANGE 时间段为空或 end <= start 3002
PAST_TIME 开始时间早于 now - 容差(30min) 3002
ADVANCE_LIMIT_EXCEEDED 开始时间晚于 now + 60 天 3002
DURATION_EXCEEDED 单段时长 > 8 小时 3002
OUTSIDE_OPENING_HOURS 不在实验室开放时间内或跨天 3002
RESOURCE_UNAVAILABLE 实验室/设备处于维护中 3002
CAPACITY_EXCEEDED 人数 > 实验室容量 3002
COURSE_REQUIRED / COURSE_LAB_MISMATCH / COURSE_NOT_ACTIVE 课程绑定校验 3002 / 2203
INTERNAL_OVERLAP 请求内部多个时间段互相重叠 3002
LAB_CONFLICT / DEVICE_CONFLICT / USER_CONFLICT 与已有占用态预约重叠 3002

分类优先级:同实验室 > 同设备 > 同用户,一条已有预约最多产生一条冲突。

4.2 区间重叠算法

采用 排序 + 双指针扫描,而不是两两比较:

输入:requested[0..n)(请求时间段,乱序),existing[0..m)(占用态已有预约)
1. 过滤非法/被忽略/非占用状态的 existing,两侧各按 start 升序排序
2. pointer = 0
3. for each existing r:
4.     while pointer < n and requested[pointer].end <= r.start: pointer++   // 完全在 r 之前的请求段
5.     for j = pointer; j < n and requested[j].start < r.end; j++:
6.         if r.overlaps(requested[j]): 记录冲突(j, r)
输出:冲突列表
  • 重叠判定:a.start < b.end && b.start < a.end,因此 10:00-12:00 与 12:00-14:00 不算冲突(首尾相接合法)。
  • 复杂度:排序 O((n+m) log(n+m)),扫描近似线性;单次预约时 n=1,实际为一次索引查询。
  • 由于 existing 按 start 单调递增,pointer 只前进不回退。

4.3 数据库层粗筛与并发控制

resources/mapper/ReservationMapper.xml 中的 selectConflicting:

SELECT ... FROM reservation
 WHERE start_time < #{end} AND end_time > #{start}          -- 区间重叠
   AND status IN (...)                                       -- 只取占用态
   AND (lab_id = ? [OR device_id = ?] [OR user_id = ?])      -- 三个维度
   [AND id <> #{ignoreId}]                                   -- 更新/审批时排除自身
   [FOR UPDATE]                                              -- 并发写场景行锁
  • 利用 (lab_id, start_time, end_time) 等索引把候选集缩小到极少量行, 精确判断交给无副作用的 ConflictDetector。
  • 创建/审批在同一个 @Transactional 中先加锁查询、再检测、最后写入, 避免「先查后写」竞态;审批时还会排除自身做二次冲突复检, 防止多条待审批预约互相重叠。

4.4 边界情况(均有单元测试覆盖)

场景 期望
已有 [9:00,11:00),请求 [11:00,13:00) 不冲突(相接)
已有 [9:00,11:00),请求 [10:59,13:00) 冲突
已有区间完全包含请求区间 冲突
请求时间段的 end == start INVALID_TIME_RANGE
开始时间等于 now - 30min 允许(容差)
开始时间等于 now + 60 天 允许(边界含)
时长恰好 8 小时 允许
跨天但结束于 00:00 允许
状态为 CANCELLED/REJECTED/COMPLETED 的预约 不参与冲突
重复规则命不中任何日期 / 超过 60 场 抛 INVALID_RECURRENCE_RULE
请求内部两段互相重叠 INTERNAL_OVERLAP

5. RBAC 权限矩阵

操作 管理员 教师 学生
查看实验室/设备/课程/日历 ✅ ✅ ✅
实验室/设备增删改、状态流转 ✅ ❌ ❌
课程增删改(仅本人课程) ✅(任意) ✅ ❌
发起单次/多次预约 ✅(自动通过) ✅(自动通过) ✅(待审批)
发起课程绑定预约 ✅ ✅(仅本人课程) ❌
审批预约 ✅(除自己) ✅(除自己) ❌
取消/开始/完成预约 ✅(任意) 仅本人 仅本人
查看预约列表 全部 全部 仅本人(强制过滤)
用户管理 ✅ ❌ ❌

实现:@RequireRole 注解 + AuthInterceptor(JWT 解析、角色校验), 服务层再做数据级权限(所有者校验、授课教师校验)。

6. 接口设计

统一响应:{ code, message, data },code = 0 表示成功。

分组 接口
认证 POST /api/auth/login、GET /api/auth/me、POST /api/auth/logout
实验室 GET /api/labs、GET /api/labs/all、GET /api/labs/{id}、GET /api/labs/available、POST/PUT/DELETE、PATCH /{id}/status
设备 GET /api/devices、GET /api/devices/by-lab/{labId}、POST/PUT/DELETE、PATCH /{id}/status
课程 GET /api/courses、GET /api/courses/all、POST/PUT/DELETE、PATCH /{id}/finish
预约 POST /api/reservations、/recurring、/course、/check;GET /api/reservations、/{id}、/calendar;`POST /{id}/approve
用户 GET/POST/PUT/DELETE /api/users/**

7. 测试设计

7.1 测试分层

层次 代表用例 目标
算法单元测试 ConflictDetectorTest(40 例)、TimeSlotTest、RecurrenceExpanderTest 全规则 + 边界值,纯函数无数据库
状态机测试 StateMachineTest 全部合法/非法迁移与终态
服务层测试 ReservationServiceTest(44 例)、LabServiceTest 等 Mockito 隔离,覆盖权限、状态同步、异常分支
Mapper 集成测试 ReservationMapperTest XML SQL 的区间语义、状态过滤、FOR UPDATE
接口集成测试 ApiIntegrationTest(12 例) MockMvc + H2 全链路:登录、RBAC、CRUD、预约生命周期、日历

7.2 覆盖率(JaCoCo 门禁)

mvn verify
  • 核心包(排除实体/DTO/VO/配置):行覆盖 98.1%,分支覆盖 92.8%
  • 门禁:行 ≥ 90%、分支 ≥ 85%,不达标构建失败
  • 报告位置:target/site/jacoco/index.html

8. 扩展功能:日历视图

  • 后端 GET /api/reservations/calendar?labId&from&to 返回区间内预约(一次查询整月)。
  • 前端「预约日历」页使用 Element Plus el-calendar 自定义日期单元格, 按状态着色(待审批/已通过/使用中/已完成),点击事件查看详情。
  • 与冲突算法共用同一份数据模型,所见即校验依据。

9. 关键设计取舍

问题 方案 原因
重复预约如何落库 系列 + 场次拆行 冲突检测/审批/日历统一为区间模型
冲突检测放数据库还是应用层 数据库索引粗筛 + 应用层精判 兼顾性能、可测试性与跨库兼容(MySQL 无排他约束)
并发双写 FOR UPDATE + 事务内复检 + 乐观锁 预约系统写冲突概率低但后果严重
权限控制 JWT 无状态 + 注解拦截 + 服务层数据权限 部署简单,规则集中可测
状态一致性 显式状态机 + 资源状态推导 避免「有预约却空闲」的脏状态