218 lines
10 KiB
Markdown
218 lines
10 KiB
Markdown
# 实验室预约与资源管理平台
|
||
|
||
面向学校实验室的场景化预约系统:统一管理**实验室**、**设备**与**实验课程**,支持
|
||
**单次预约 / 多次预约 / 课程绑定预约**三种业务模式,内置可测试的**时间冲突检测算法**、
|
||
**资源状态机**与**基于角色的访问控制(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 文件库,无需安装数据库)
|
||
|
||
```bash
|
||
./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 时:
|
||
|
||
```bash
|
||
# 先创建数据库: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. 启动前端
|
||
|
||
```bash
|
||
cd frontend
|
||
pnpm install
|
||
pnpm dev # http://localhost:5173,已配置 /api 代理到 8080
|
||
pnpm build # 生产构建(vue-tsc 类型检查 + vite build)
|
||
```
|
||
|
||
### 3. Docker 一键部署(推荐用于演示/验收)
|
||
|
||
```bash
|
||
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`。**先显式构建镜像即可解决**:
|
||
|
||
```bash
|
||
./scripts/podman-deploy.sh up # 一键:构建镜像 -> 启动 -> 健康检查 -> 打印访问地址
|
||
./scripts/podman-deploy.sh down # 停止(保留数据库数据卷)
|
||
./scripts/podman-deploy.sh down-v # 停止并清空数据库
|
||
./scripts/podman-deploy.sh logs # 查看日志
|
||
```
|
||
|
||
手动等价步骤:
|
||
|
||
```bash
|
||
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`
|
||
- **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](docs/design.md)。
|
||
|
||
### 状态机
|
||
|
||
- 实验室/设备:`IDLE → RESERVED → IN_USE → IDLE`,`IDLE ↔ MAINTENANCE`,非法迁移抛 `3003`
|
||
- 预约:`PENDING → APPROVED → IN_USE → COMPLETED`,`PENDING → REJECTED`,
|
||
`PENDING/APPROVED → CANCELLED`,终态不可再流转
|
||
|
||
## 测试与覆盖率
|
||
|
||
```bash
|
||
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/**` | 用户管理 | 管理员 |
|
||
|
||
统一响应格式:
|
||
|
||
```json
|
||
{ "code": 0, "message": "success", "data": {} }
|
||
```
|
||
|
||
业务错误码:`0` 成功、`1xxx` 用户与认证、`2xxx` 资源管理、`3xxx` 预约与冲突、`4xx/5xx` 通用错误。
|