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基础,便于排查故障和运维。

准备:

  1. 前端打包文件,名字:dist.zip
  2. 后端打包文件,名字:gpu.jar(注意这里的名字和Dockerfile对应)
  3. 创建数据卷挂载目录。

首先完成数据卷挂载的准备工作:

# 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脚本的办法。

  1. 用FTP上传sql脚本。reserve_system.sql
  2. 将sql放到容器内。执行:docker cp reserve_system.sql gpu-mysql:/home
  3. 进入容器运行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 APIOptions API两种开发风格,本项目采用前者。

后端

  1. 权限管理借鉴了 RBAC权限模型的思想,简单来说,可以理解为存在用户表、角色表、权限表,以及用户角色关联表、权限角色关联表五张表。
  2. 项目采用MVC分层思想开发,将实体类拆分成 ViewDTOEntity三个部分,但由于项目体量较小,有少部分实体因功能较少便没有做严格区分。
  3. 登录认证基于Spring Security和json web token 的技术。
  4. GPU状态的实现原理:Java程序通过RestTemplate像Python程序发送HTTP请求,并将数据存入Redis缓存数据库。前端的请求直接从Redis中进行读取。