项目:基于Vue3+SpringBoot的GPU预约系统
2024-01-16:
启动V2.0重构计划,已写好重构方案和文档,准备实施。2023-11-09:
作为我的第一个全栈项目,这个项目V1.0确实不太成熟,后续有机会的话我可能会重构这个项目的后端,并开源到GitHub上。
前言:
记录一个最近完成的项目:前后端分离的GPU显卡预约系统。
在本次项目中,我负责数据库设计+前端开发+后端开发+部署运维+文档书写。
由于本项目是供实验室内部使用,目前没有开源,后期进一步完善后会考虑开源。
Reserve System
项目简介
Reserve System是一个基于Spring Boot + Vue3 + Flask 的前后端分离GPU显卡预约系统。
项目结构
整体结构
├─backend // 后端代码
├─flask // Python代码
├─frontend // 前端代码
├─sql // 数据库脚本
│ └── reserve_system.sql // 初始化脚本
├─dist.zip // 打包后的前端应用
├─docker-compose.yml // 供参考的docker-compose文件
├─Dockfile // 用于构建java应用镜像
├─gpu.jar // 打包后的后端应用
├─README.md // 项目文档
前端结构
├─node_modules // Node.js依赖
├─package.json // Node.js配置文件
├─src // 存放前端核心代码
│ └── api // 全局Api封装
│ └── api.js // 全局Api封装
│ └── request.js // 二次封装axios,HTTP请求相关
│ └── assets // 图片资源和css资源
│ └── components // 公共组件
│ └── CommonAside.vue // 侧边栏
│ └── CommonHeader.vue // 导航栏
│ └── doc // 文档
│ └── router // 路由
│ └── index.js // 负责映射路由与组件
│ └── stores // 状态管理,采用Pinia
│ └── global.js // 保存网站基本信息
│ └── gpu.js // 保存GPU信息
│ └── menu.js // 保存菜单信息
│ └── reserve.js // 保存预约信息
│ └── user.js // 保存用户信息
│ └── views // 视图组件
│ └── admin // 管理员相关界面
│ └── Global.vue // 网站基本信息配置
│ └── GPU.vue // GPU管理
│ └── Order.vue // 工单审批
│ └── Server.vue // 服务器管理
│ └── User.vue // 用户管理
│ └── Bulletin.vue // 公告管理
│ └── home // 主页
│ └── Home.vue // 主页
│ └── reserve // 预约相关界面
│ └── My.vue // 我的预约
│ └── Order.vue // 工单系统
│ └── Query.vue // 预约中心
│ └── Status.vue // GPU状态查询
│ └── Login.vue // 登录界面
│ └── Bulletin.vue // 公告界面
│ └── Main.vue // 主界面,挂载所有组件
│ └── App.vue // 根组件
│ └── main.js // 程序入口
后端结构
包名:cn.edu.scu
Java目录:
├─config // 配置类
│ └── FastJsonRedisSerializer // Redis序列化配置
│ └── MybatisConfig // Mybatis配置类
│ └── RedisConfig // Redis配置类
│ └── SecurityConfig // Spring Security配置类
│ └── WebConfig // Web配置类,配置FastJSON、跨域
│ └── RestTemplateConfig // Rest配置类,用于发送HTTP请求
│ └── SwaggerConfig // Swagger接口文档配置类
├─controller // 控制器,处理请求
│ └── AdminController // 处理后台相关请求
│ └── GpuController // 处理Gpu相关请求
│ └── BulletinController // 处理公告相关请求
│ └── LoginController // 处理登录注销请求
│ └── OrderController // 处理工单请求
│ └── ReserveController // 处理预约相关请求
│ └── ServerController // 处理服务器相关请求
│ └── UserController // 处理用户相关请求
├─dto // DTO,前端传参的实体
├─views // VO,展示的实体
├─entity // 实体类,与数据库对应,此三者类似,只展示entity
│ └── Global // 网站基本信息
│ └── Bulletin // 公告
│ └── GpuStatus // 每个卡的GPU状态
│ └── GpuStatusParent // 每个服务器的GPU状态
│ └── Process // 每个进程的信息
│ └── Gpu // GPU信息
│ └── LoginUser // 登录后的用户,封装了权限信息
│ └── Menu // 菜单
│ └── Reserve // 预约信息
│ └── ResponseResult // 封装了全局返回的数据格式
│ └── Role // 用户角色信息
│ └── Server // 服务器信息
│ └── User // 用户信息
├─enums // 枚举
│ └── AppHttpCodeEnum // 业务状态码
├─exception // 异常
│ └── SystemException // 自定义异常
├─filter // 过滤器
│ └── JwtAuthenticationTokenFilter // JWT认证过滤器,登陆前拦截
├─handler // 处理器
│ └── AccessDeniedHandlerImpl // 权限处理器
│ └── AuthenticationEntryPointImpl // 身份验证处理器
│ └── GlobalExceptionHandler // 全局异常处理器
├─job // 定时任务
│ └── UpdateGPUStatusJob // 更新GPU状态信息
├─mapper // 用于查询数据库
│ └── GlobalMapper // 网站基本信息
│ └── GpuMapper // GPU信息
│ └── MenuMapper // 菜单
│ └── ReserveMapper // 预约信息
│ └── RoleMapper // 用户角色信息
│ └── ServerMapper // 服务器信息
│ └── UserMapper // 用户信息
├─service // 服务类,执行核心业务逻辑
│ └── impl // 服务接口的实现类
│ └── ... // 包含下面所有接口的实现类
│ └── PermissionService // 权限服务
│ └── UserDetailService // 认证服务
│ └── GpuStatusServiceImpl // GPU状态服务
│ └── AdminService // 后台服务
│ └── BulletinService // 公告服务
│ └── GlobalService // 网站信息服务
│ └── GpuService // GPU服务
│ └── LoginService // 登录服务
│ └── MenuService // 菜单服务
│ └── ReserveService // 预约服务
│ └── RoleService // 角色预约
│ └── ServerService // 服务器服务
│ └── UserSerivce // 用户服务
├─utils // 工具类
│ └── BeanCopyUtils // 负责实体类的拷贝
│ └── JWTUtil // JWT的生成、解析
│ └── RedisCache // Redis
│ └── SecurityUtils // Spring Security工具类
│ └── WebUtils // 工具类,渲染字符串等
├─ReserveSystemApplication.java // 启动程序,程序入口
resources目录:
├─mapper // SQL语句映射文件
│ └── MenuMapper.xml // 菜单表相关
│ └── RoleMapper.xml // 角色表相关
├─application.yml // 配置文件
├─application-dev.yml // 开发环境配置文件
├─application-prod.yml // 生产环境配置文件
备注:同一个实体类,例如User,需要拆分成为UserDTO、UserView、User三个类,用于实现解耦合。其中DTO用于接收前端传来的参数,View用于返回给前端需要的数据,User本身作为实体类与数据库之间的映射关系。
Python程序结构
基于Flask的Python程序用于GPU状态的监控,并将数据返回给Spring Boot应用。
├─gpu.py // 获取GPU状态信息的flask小程序
├─config.yml // 配置文件,配置服务器ID、端口
├─start.sh // 启动脚本
技术选型和环境
前端
功能 | 技术 |
---|---|
前端核心框架 | Vue3.x |
运行环境 | Node.js v18.12.1 |
构建工具 | Vite |
状态管理 | Pinia |
网络请求 | Axios |
UI框架 | Element Plus |
UI组件库 | FullCalendar |
路由管理 | Vue Router |
Markdown渲染 | Markdown-It |
后端
功能 | 技术 |
---|---|
开发语言 | Java8 |
后端核心框架 | Spring Boot 2.5.0 |
构建工具 | Maven |
安全框架 | Spring Security |
ORM框架 | MyBatis-Plus |
Token | jjwt |
关系型数据库 | MySQL 5.7 |
缓存数据库 | Redis |
Api文档 | Swagger |
Python
功能 | 技术 |
---|---|
开发语言 | Python3.7 |
部署服务器 | gunicorn |
虚拟环境 | Anaconda |
Web框架 | Flask |
GPU信息获取 | gpustat |
配置项
Spring Boot
后端采用生产环境和开发环境分开的配置文件。
首先需要在 application.yml
中指定环境,dev
表示开发环境,prod
表示生产环境。此外,application.yml
中可以指定服务监听端口,本项目中设置为 7777
,如果需要修改此端口,请保证 docker-compose.yml
文件中的端口也一并修改。
# 服务器信息
server:
port: 7777 # 服务器端口
# 配置生产环境和开发环境
spring:
profiles:
active: prod
在 application-dev.yml
中,可以设置本地数据库信息,用于开发环境。
默认本地的redis启用的主机名是localhost,所以在这个文件没有配置。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/reserve_system?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
在 application-prod.yml
文件中,可以设置远程的数据库信息,用于生产环境。这里 gpu-mysql
是docker中MySQL容器的名字。redis.host
配置docker中redis容器的名字。
spring:
datasource:
url: jdbc:mysql://gpu-mysql:3306/reserve_system?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: gpu-redis
Python配置项
Flask应用的config.yml文件配置如下:
port: 11200 # Flask监听的端口
server:
id : 1 # 本台服务器ID,与预约系统的服务器ID对应,用于展示
ip: # 本机内网IP,应该是192.168.1开头
安装和部署
前端
本地运行
本项目采用vite进行构建,本地运行需要有 Node18 的运行环境。
# 安装Node依赖
yarn
# 启动
yarn dev
单独部署
构建项目,生成dist文件夹。将此文件夹打包为dist.zip。
# 构建项目,会生成/dist
yarn build
在服务器上安装nginx,直接sudo apt安装即可。
将dist.zip上传到 /usr/share/nginx/html
,配置nginx.conf文件,部分配置参考如下。(这里需要有nginx的基础)
http {
server {
listen 80;
location / {
root /usr/share/nginx/html/dist;
try_files $uri $uri/ /index.html;
}
}
}
Flask小程序(单独部署)
Flask 小程序用于监控GPU的运行情况,每台服务器需单独部署和配置。
前提:
- 服务器需要安装Anaconda环境。
- 将程序上传到自己的服务器单独目录。
首先执行下面命令,创建conda虚拟环境。
# 安装虚拟环境
conda create --name gpu_monitor_py37 python=3.7
# 启动环境
conda activate gpu_monitor_py37
# 安装依赖
pip install flask
pip install gunicorn
pip install pyyaml
# 部分情况下需要安装gpustat
pip install gpustat
配置config.yml:见上面【配置项】部分。
启动脚本:sh start.sh
后端-单独部署
建议:通过InteliJ IDEA进行开发和打包。命令行打包有时会出错。
注意:服务器需要有jdk1.8的环境
打包:在InteliJ IDEA中右侧菜单栏的Maven插件中,先点击 clean
,再点击 install
,会在target目录下生成对应的jar包。
将jar包上传到服务器,执行java命令部署:
java -jar jar包名.jar
Docker一键部署(推荐)
下面教程假设pwd为用户自己新建的项目目录,以目录名gpu为例。
最好有一定的docker基础,便于排查故障和运维。
准备:
- 前端打包文件,名字:dist.zip
- 后端打包文件,名字:gpu.jar(注意这里的名字和Dockerfile对应)
- 创建数据卷挂载目录。
首先完成数据卷挂载的准备工作:
# mysql 挂载准备
mkdir mysql
cd mysql
mkdir data
mkdir init
mkdir conf
cd ..
# nginx 挂载准备
mkdir nginx
cd nginx
mkdir html
# nginx配置文件
vim nginx.conf # 根据需求写配置文件,默认用80端口
# 将dist文件放到nginx/html并解压
cd ..
mv dist.zip nginx/html
cd nginx/html
unzip dist.zip
rm -rf dist.zip
Dockerfile用于构建Java项目的镜像,docker-compose用于一键部署nginx镜像、Java项目镜像、MySQL镜像、Redis镜像。
部署前,请检查当前目录是否有下面文件和目录,如果没有,请自行通过FTP上传。
gpu.jar
(打包后的java程序)mysql
(前面步骤创建的目录)nginx
(前面步骤创建的目录)Dockerfile
docker-compose.yml
如果没问题,执行下面命令构建并运行容器:
docker compose up -d
检查容器情况,执行下面命令,如果redis、MySQL、Java、nginx四个容器都启动了,说明部署成功。
docker compose ps
接下来是导入数据库数据,按理说将SQL脚本放到 mysql/init
下即可,因为在 docker-compose.yml
文件中已经挂载过了,但我试过一直有问题。因此下面介绍进入容器执行SQL脚本的办法。
- 用FTP上传sql脚本。
reserve_system.sql
。 - 将sql放到容器内。执行:
docker cp reserve_system.sql gpu-mysql:/home
- 进入容器运行MYSQL,执行下面命令。
docker exec -it gpu-mysql mysql -uroot -p # 进入容器
# 下面是进入MySQL终端后执行的命令
CREATE DATABASE IF NOT EXISTS reserve_system CHARACTER SET utf8mb4
use reserve_system
source /home/reserve_system.sql
最后,查看各个容器的日志,观察服务是否正常启动,尤其是Java程序,可以观察有无报错信息。
docker logs gpu-mysql -f # 查看MySQL运行日志
docker logs gpu-java -f # 查看Java运行日志
docker logs gpu-redis -f # 查看Redis运行日志
docker logs nginx -f # 查看Nginx运行日志
到这里,整个应用就部署完毕了,在确认服务器开放了80端口后,可以前往:http://ip/
,查看网站是否正常运行(如果有域名,应该配置到Nginx中)
Swagger 接口文档使用指南
本项目采用Swagger2接口文档。Spring Boot程序启动后,程序会自动生成接口文档。
使用方式:访问 http://localhost:7777/swagger-ui.html
Swagger UI在Java项目中的注解说明如下:
@Api
:为某个controller提供说明。@ApiOperation
:为某个接口提供说明。@ApiImplicitParams
:为接口的参数提供说明。@ApiModel
:为某个实体类提供说明。@ApiModelProperty
:为某个实体类的属性提供说明。
项目其它说明
前端
Vue3有 Composition API
和 Options API
两种开发风格,本项目采用前者。
后端
- 权限管理借鉴了
RBAC权限模型
的思想,简单来说,可以理解为存在用户表、角色表、权限表,以及用户角色关联表、权限角色关联表五张表。 - 项目采用MVC分层思想开发,将实体类拆分成
View
、DTO
、Entity
三个部分,但由于项目体量较小,有少部分实体因功能较少便没有做严格区分。 - 登录认证基于Spring Security和json web token 的技术。
- GPU状态的实现原理:Java程序通过RestTemplate像Python程序发送HTTP请求,并将数据存入Redis缓存数据库。前端的请求直接从Redis中进行读取。
- 感谢你赐予我前进的力量