实验室预约与资源管理平台
面向学校实验室的场景化预约系统:统一管理实验室、设备与实验课程,支持 单次预约 / 多次预约 / 课程绑定预约三种业务模式,内置可测试的时间冲突检测算法、 资源状态机与基于角色的访问控制(RBAC),并提供 Vue3 前端与日历视图。
技术栈
| 层次 | 技术 |
|---|---|
| 后端 | Spring Boot 4.1.1、MyBatis-Plus 3.5.17(XML 自定义 SQL + QueryWrapper)、JWT(jjwt)、BCrypt |
| 数据库 | H2(默认,免安装演示)/ MariaDB(mariadb Profile,schema 脚本已提供) |
| 测试 | JUnit 5、Mockito、MockMvc、AssertJ、JaCoCo(覆盖率门禁) |
| 前端 | Vue 3 + TypeScript + Vite + Element Plus + Pinia + Vue Router + Axios,包管理 pnpm |
快速开始
1. 启动后端(默认 H2 文件库,无需安装数据库)
./mvnw spring-boot:run
# 或者:mvn spring-boot:run
- 服务地址:http://localhost:8080
- H2 控制台:http://localhost:8080/h2-console (JDBC URL:
jdbc:h2:file:./data/labdb,用户sa,密码留空) - 首次启动会自动建表并写入演示数据(
src/main/resources/db/schema.sql、data.sql)
使用 MariaDB/MySQL 时:
# 先创建数据库:CREATE DATABASE lab DEFAULT CHARSET utf8mb4;
DB_HOST=localhost DB_USER=root DB_PASSWORD=yourpass \
mvn spring-boot:run -Dspring-boot.run.profiles=mariadb
2. 启动前端
cd frontend
pnpm install
pnpm dev # http://localhost:5173,已配置 /api 代理到 8080
pnpm build # 生产构建(vue-tsc 类型检查 + vite build)
3. Docker 一键部署(推荐用于演示/验收)
cp .env.example .env # 可选:按需修改端口/密码/JWT 密钥
docker compose up -d --build # 构建并启动 mariadb + backend + frontend
# 前端:http://localhost:8088 (Nginx 托管静态资源并反代 /api)
# 后端:http://localhost:8080
docker compose ps # 查看健康状态
docker compose down # 停止容器,保留数据库数据卷
docker compose down -v # 停止容器并删除数据目录(下次启动重建演示数据)
- 编排:
mariadb:11.4(数据持久化到命名卷)+ 后端多阶段构建镜像(JRE 21,非 root 运行)+ Nginx 前端镜像 - 三个服务均配置健康检查,后端与前端会等待上游就绪后再启动
- 国内加速:后端构建自动使用阿里云 Maven 镜像(
docker/maven-settings.xml), 前端使用 npmmirror(淘宝 npm 镜像);如需走官方源:docker build --build-arg MAVEN_MIRROR=false .、docker build --build-arg NPM_REGISTRY=https://registry.npmjs.org ./frontend - 可用环境变量(见
.env.example):WEB_PORT(默认 80)、BACKEND_PORT(默认 8080)、DB_NAME/DB_USER/DB_PASSWORD/DB_ROOT_PASSWORD、LAB_JWT_SECRET、JAVA_OPTS
4. Podman 部署
Podman 的 compose 实现与 Docker Compose 在「镜像名/容器名解析」上存在差异,
直接 podman compose up -d 可能出现以下报错:
Error: short-name "lab-reservation_backend" did not resolve to an alias ...
Error: "lab-backend" is not a valid container, cannot be used as a dependency ...
原因是 compose 未先构建本地镜像,Podman 便把 lab-reservation_backend 当作待拉取的短名镜像,
而系统未配置 unqualified-search-registries。先显式构建镜像即可解决:
./scripts/podman-deploy.sh up # 一键:构建镜像 -> 按序启动 -> 健康检查 -> 打印访问地址
./scripts/podman-deploy.sh status # 查看容器状态
./scripts/podman-deploy.sh down # 停止(保留数据目录)
./scripts/podman-deploy.sh down-v # 停止并删除数据目录(重建演示数据)
./scripts/podman-deploy.sh logs # 跟踪后端日志
./scripts/podman-deploy.sh diagnose # 部署失败时输出诊断信息(镜像/容器/日志)
脚本做了四件关键的事,规避 podman-compose 的兼容性问题:
podman build显式构建lab-backend:latest/lab-frontend:latest,避免短名拉取;- 启动前自动生成
.podman-compose.generated.yml(从docker-compose.yml中剔除depends_on), 避免旧版 podman-compose 在up时等待依赖健康检查而卡住; - 按「数据库 → 后端 → 前端」逐个启动并等待就绪(MariaDB 用
healthcheck.sh,后端/前端用 HTTP 探测); down直接按容器名清理,避免对不存在容器的报错噪音。
防卡死:所有外部命令都带超时,任何一步超时会打印原因并退出,不会无限等待。
可用环境变量调节:CMD_TIMEOUT(单条命令,默认 60s)、BUILD_TIMEOUT(构建,默认 1800s)、
READY_TRIES(就绪轮询次数,默认 60 次 × 2s);用 DEBUG=1 ./scripts/podman-deploy.sh up
可以看到每一步实际执行的命令,定位卡在哪一步。
镜像已构建过时可跳过构建:./scripts/podman-deploy.sh up --no-build。
若已有历史失败状态,先清理再重来:
./scripts/podman-deploy.sh down-v && ./scripts/podman-deploy.sh up
手动等价步骤:
podman build -t lab-backend:latest .
podman build -t lab-frontend:latest ./frontend
podman compose up -d # 或 podman-compose up -d
Podman 注意事项:
- 端口:前端默认映射到
8088(rootless Podman 无法绑定 80 等特权端口), 如需 80 可WEB_PORT=80 ./scripts/podman-deploy.sh up,或将net.ipv4.ip_unprivileged_port_start=0写入/etc/sysctl.d/后sysctl --system - 短名镜像:本项目所有基础镜像已写成全限定名(
docker.io/library/...)。 若自行添加镜像,建议同样全限定,或在~/.config/containers/registries.conf配置unqualified-search-registries = ["docker.io"] - 旧版 podman-compose:若提示不支持
depends_on.condition,请升级到 1.0.6+, 或先podman-compose up -d mariadb,等数据库健康后再podman-compose up -d backend frontend - 数据目录:数据库数据以 bind mount 方式保存在
DATA_DIR(默认./mariadb-data,已在.gitignore中忽略),down-v会删除该目录;目录中含历史 binlog 等文件时属正常现象 - SELinux(Fedora/RHEL):挂载已带
:Z自动打标;若自定义其他 bind mount 请同样加:Z
5. 演示账号
| 账号 | 密码 | 角色 | 典型能力 |
|---|---|---|---|
admin |
admin123 |
管理员 | 全部资源与用户管理、审批、状态流转 |
teacher1 |
123456 |
教师 | 课程管理、发起课程预约、审批 |
student1 |
123456 |
学生 | 发起预约、查看/取消自己的预约 |
功能地图
- 认证与权限:JWT 登录、
@RequireRole角色注解、拦截器统一鉴权,前端路由/菜单按角色过滤 - 实验室管理:增删改查、状态机流转(空闲 / 已预约 / 使用中 / 维护中)、可用实验室推荐
- 设备管理:增删改查、归属实验室校验、状态机流转
- 实验课程:教师排课(起止日期 + 星期 + 时间段)、一键生成整期课程绑定预约
- 预约中心:
- 单次预约(datetimerange)
- 多次预约(按周重复,自动拆分为系列场次)
- 课程绑定预约(按课程排课规则批量生成)
- 冲突预检、审批(通过/驳回)、取消(可整系列)、开始/完成使用
- 日历视图:按实验室 + 月份展示预约分布,点击查看详情
- 自动任务:每 10 分钟自动完结已过期的预约并同步资源状态
核心设计
冲突检测
分层设计:数据库层用索引粗筛(XML 自定义 SQL,可选 FOR UPDATE 行锁),
应用层 ConflictDetector 做精确判断。
- 区间语义:左闭右开
[start, end),首尾相接不算冲突 - 规则校验:时间区间合法性、过去时间、提前预约上限、单次时长、开放时间/跨天、 资源维护状态、容量、课程绑定关系、请求内部重叠
- 冲突分类:
LAB_CONFLICT/DEVICE_CONFLICT/USER_CONFLICT等,返回多冲突明细 - 并发控制:同事务内
SELECT ... FOR UPDATE+ 冲突复检,避免并发双写
详细说明见 docs/design.md。
状态机
- 实验室/设备:
IDLE → RESERVED → IN_USE → IDLE,IDLE ↔ MAINTENANCE,非法迁移抛3003 - 预约:
PENDING → APPROVED → IN_USE → COMPLETED,PENDING → REJECTED,PENDING/APPROVED → CANCELLED,终态不可再流转
测试与覆盖率
mvn verify # 运行全部测试并执行 JaCoCo 覆盖率门禁
- 测试规模:219 个用例(19 个测试类)(冲突算法边界、状态机、服务层 Mockito、Mapper/接口集成)
- 覆盖率(核心包,排除实体/DTO/VO/配置):行 98.1%,分支 92.8%
- 门禁:行 ≥ 90%,分支 ≥ 85%,不达标
mvn verify直接失败 - 报告:
target/site/jacoco/index.html
目录结构
├── src/main/java/com/example/lab
│ ├── common/ # 统一响应、异常、注解、上下文、JWT
│ ├── config/ # MyBatis-Plus、Web、安全、业务参数
│ ├── controller/ # REST 接口
│ ├── domain/
│ │ ├── conflict/ # 时间复杂度可控的冲突检测与重复规则展开
│ │ ├── entity/ # 实体
│ │ ├── enums/ # 角色/状态/类型枚举
│ │ └── statemachine/# 通用枚举状态机
│ ├── dto/ vo/ # 请求与视图对象
│ ├── mapper/ # MyBatis-Plus Mapper
│ ├── security/ # 认证授权拦截器
│ └── service/ # 业务服务
├── src/main/resources
│ ├── db/ # H2 / MariaDB 建表与演示数据
│ └── mapper/ # XML 自定义 SQL
├── src/test/java/... # 单元测试 + 集成测试
├── frontend/ # Vue3 + Element Plus 前端
│ ├── Dockerfile # pnpm 构建 + Nginx 托管(含 /api 反向代理)
│ └── nginx.conf
├── Dockerfile # 后端多阶段构建镜像
├── docker-compose.yml # mariadb + backend + frontend 一键编排
├── .env.example # 部署环境变量示例(复制为 .env 使用)
├── scripts/podman-deploy.sh # Podman 一键部署脚本
└── docs/design.md # 需求分析与详细设计
主要接口
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| POST | /api/auth/login |
登录 | 公开 |
| GET | /api/auth/me |
当前用户 | 登录 |
| GET | /api/labs / /api/labs/available |
实验室列表 / 可用推荐 | 登录 |
| POST/PUT/DELETE | /api/labs/** |
实验室管理 | 管理员 |
| GET | /api/devices / /api/devices/by-lab/{id} |
设备列表 | 登录 |
| POST/PUT/DELETE | /api/devices/** |
设备管理 | 管理员 |
| GET | /api/courses |
课程列表 | 登录 |
| POST/PUT/DELETE | /api/courses/** |
课程管理 | 管理员/教师 |
| POST | /api/reservations |
单次预约 | 登录 |
| POST | /api/reservations/recurring |
多次预约 | 登录 |
| POST | /api/reservations/course |
课程绑定预约 | 管理员/授课教师 |
| POST | /api/reservations/check |
冲突预检 | 登录 |
| GET | /api/reservations / /{id} / /calendar |
列表 / 详情 / 日历 | 登录(学生仅本人) |
| POST | /api/reservations/{id}/approve|reject |
审批 | 管理员/教师 |
| POST | /api/reservations/{id}/cancel|start|complete |
取消/开始/完成 | 本人或管理员 |
| GET/POST/PUT/DELETE | /api/users/** |
用户管理 | 管理员 |
统一响应格式:
{ "code": 0, "message": "success", "data": {} }
业务错误码:0 成功、1xxx 用户与认证、2xxx 资源管理、3xxx 预约与冲突、4xx/5xx 通用错误。