2026-09-21 13:33:46 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 13:33:46 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 13:23:30 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 13:23:30 +08:00
2026-09-21 13:17:14 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 12:51:08 +08:00
2026-09-21 12:51:08 +08:00

实验室预约与资源管理平台

面向学校实验室的场景化预约系统:统一管理实验室、设备与实验课程,支持 单次预约 / 多次预约 / 课程绑定预约三种业务模式,内置可测试的时间冲突检测算法、 资源状态机与基于角色的访问控制(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

使用 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 的兼容性问题:

  1. podman build 显式构建 lab-backend:latest / lab-frontend:latest,避免短名拉取;
  2. 启动前自动生成 .podman-compose.generated.yml(从 docker-compose.yml 中剔除 depends_on), 避免旧版 podman-compose 在 up 时等待依赖健康检查而卡住;
  3. 按「数据库 → 后端 → 前端」逐个启动并等待就绪(MariaDB 用 healthcheck.sh,后端/前端用 HTTP 探测);
  4. 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 通用错误。

Description
No description provided
Readme 278 KiB
Languages
Java 77.7%
Vue 14.5%
TypeScript 4.4%
Shell 2.6%
Dockerfile 0.7%