项目工程结构

项目采用普通的SpringBoot项目结构,其中common模块下是全局异常处理和统一返回结果。server模块则是整个项目的业务代码。

easypan
├── common
│ ├── common-util
│ └── pom.xml
├── learning-online-content
└── server

根据项目结构创建出父工程easypan,删除其中的src目录,并两个子模块commonserver

版本控制

在父工程easypan的pom文件中添加版本依赖信息

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.swx</groupId>
<artifactId>easypan</artifactId>
<version>1</version>
<packaging>pom</packaging>
<name>easypan</name>
<description>easypan</description>
<modules>
<module>common</module>
<module>server</module>
</modules>

<properties>
<springboot.version>2.6.1</springboot.version>
<logback.version>1.2.10</logback.version>
<mysql.version>8.0.30</mysql.version>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<aspectjweaver.version>1.9.4</aspectjweaver.version>
<fastjson.version>2.0.21</fastjson.version>
<commons.lang3.version>3.4</commons.lang3.version>
<commons.codec.version>1.9</commons.codec.version>
<commons.io.version>2.5</commons.io.version>
<ws.schild.version>3.3.1</ws.schild.version>
</properties>

<dependencyManagement>
<dependencies>
<!--邮件发送-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>${springboot.version}</version>
</dependency>
<!--redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${springboot.version}</version>
</dependency>

<!-- 数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>


<!-- 日志版本 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>

<!--切面-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<!--apache common-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!--FFmpeg-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>${ws.schild.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<finalName>${project.name}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

统一返回结果

通过拦截Controller的返回结果和全局异常,将其封装成统一的格式,并使用RestController返回JSON形式的数据给前端。

{
"code":20000,
"message":"成功",
"data":{
"info":"测试成功"
}
}

项目介绍地址:

SpringBoot统一封装返回结果和异常情况

将GitHub代码下载到本地,将其中的common-util放到common模块下,作为其子模块。

业务代码工程

引入依赖

server模块的pom文件中添加依赖信息

pom.xml
<dependencies>
<dependency>
<groupId>com.swx</groupId>
<artifactId>common-util</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!--邮件发送-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

<!--redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>


<!-- 日志版本 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>

<!--切面-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>

<!--apache common-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- Mybatis-Plus自动生成代码 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
<scope>test</scope>
</dependency>
<!-- 模版引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- FFmpeg -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
</dependency>
</dependencies>

启动类

创建 EasyPanApplication

EasyPanApplication
@EnableAsync
@EnableScheduling
@EnableTransactionManagement
public class EasyPanApplication {
public static void main(String[] args) {
SpringApplication.run(EasyPanApplication.class, args);
}
}

配置文件

创建application.properties配置文件

# 应用服务 WEB 访问端口
server.port=7090
server.servlet.context-path=/api
#session过期时间 60M 一个小时
server.servlet.session.timeout=PT60M
#处理favicon
spring.mvc.favicon.enable=false
#异常处理
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
spring.servlet.multipart.max-file-size=15MB
spring.servlet.multipart.max-request-size=15MB
#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/easypan?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=swx852345
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.pool-name=HikariCPDatasource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
# mybatis plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
mybatis-plus.mapper-locations=classpath:mapper/*.xml
#发送邮件配置相关
# 配置邮件服务器的地址 smtp.qq.com
spring.mail.host=smtp.qq.com
# 配置邮件服务器的端口(465或587)
spring.mail.port=465
# 配置用户的账号
spring.mail.username=2627311935@qq.com
# 配置用户的密码
spring.mail.password=tykyolipdlapdifi
# 配置默认编码
spring.mail.default-encoding=UTF-8
# SSL 连接配置
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# 开启 debug,这样方便开发者查看邮件发送日志
spring.mail.properties.mail.debug=true
#邮件配置结束
#Spring redis配置
# Redis数据库索引(默认为0)
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=swx852345
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=2000
#项目目录
project.folder=/Users/swcode/Documents/webser/web_app/easypan
#日志级别配置
log.root.level=info
#超级管理员id
admin.emails=test@qq.com
#是否是开发环境
dev=false
##qq登陆相关##
qq.app.id=12333
qq.app.key=2222222
qq.url.authorization=https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s
qq.url.access.token=https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
qq.url.openid=https://graph.qq.com/oauth2.0/me?access_token=%S
qq.url.user.info=https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s
qq.url.redirect=http://easypan.wuhancoder.com/qqlogincalback

日志文件,logback-spring.xml

logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10 minutes">
<appender name="stdot" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%c][%M][%L]-> %m%n</pattern>
</layout>
</appender>

<springProperty scope="context" name="log.path" source="project.folder"/>
<springProperty scope="context" name="log.root.level" source="log.root.level"/>

<property name="LOG_FOLDER" value="logs"/>
<property name="LOG_FILE_NAME" value="easypan.log"/>

<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${LOG_FOLDER}/${LOG_FILE_NAME}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${LOG_FOLDER}/${LOG_FILE_NAME}.%d{yyyyMMdd}.%i</FileNamePattern>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>20MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<charset>utf-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%c][%M][%L]-> %m%n</pattern>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>

<root level="${log.root.level}">
<appender-ref ref="stdot"/>
<appender-ref ref="file"/>
</root>

</configuration>

配置类

创建com.swx.easypan.config包。

Mybatis Plus

com.swx.easypan.config包下创建Mybatis Plus的配置文件

MybatisPlusConfig
@MapperScan(basePackages = {"com.swx.easypan.mapper"})
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {

@Bean
public MetaObjectHandler metaObjectHandler() {
return new MyMetaObjectHandler();
}

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

自动填充

MyMetaObjectHandler
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
boolean createTime = metaObject.hasSetter("createTime");
boolean updateTime = metaObject.hasSetter("updateTime");
if (updateTime) {
strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
if (createTime) {
strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
}

@Override
public void updateFill(MetaObject metaObject) {
boolean updateTime = metaObject.hasSetter("updateTime");
if (updateTime) {
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
}

配置文件变量类

AppConfig
@Component("appConfig")
public class AppConfig {

@Value("${spring.mail.username}")
private String sendUsername;

@Value("${admin.emails}")
private String emails;

@Value("${project.folder}")
private String projectFolder;

public String getSendUsername() {
return sendUsername;
}

public String getEmails() {
return emails;
}
public String getProjectFolder() {
return projectFolder;
}
}

代码生成

在测试目录中创建代码生成器,运行后会在server模块中创建Mybatis Plus的基础代码

CodeGenerator
public class CodeGenerator {

public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/server/src/main/java");
gc.setAuthor("sw-code");
gc.setOpen(false); // 是否打开文件资源管理器
gc.setFileOverride(false); // 是否覆盖
gc.setServiceName("%sService"); // 去Service的I前缀
gc.setIdType(IdType.AUTO); // 主键策略
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql:///easypan?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8&nullCatalogMeansCurrent=true");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("swx852345");
mpg.setDataSource(dsc);

// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.swx.easypan");
pc.setEntity("pojo");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("file_share", "file_info", "user_info", "email_code"); // 加入要生成的表,逗号分隔
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setLogicDeleteFieldName("deleted"); // 逻辑删除
// 自动填充
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
List<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");

strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);

mpg.execute();
}
}

工具类

创建包 com.swx.easypan.utils,以下工具方法都放在该包下

StringTools

StringTools
public class StringTools {

/**
* 生成随机数
* @param count 位数
*/
public static String getRandomNumber(Integer count) {
return RandomStringUtils.random(count, false, true);
}

public static String getRandomString(Integer count) {
return RandomStringUtils.random(count, true, true);
}

public static String rename(String filename) {
return getFilename(filename) + "_" + getRandomString(Constants.LENGTH_5) + getFileSuffix(filename);
}

public static String getFilename(String filename) {
int index = filename.lastIndexOf(".");
if (index == -1) {
return filename;
}
return filename.substring(0, index);
}

public static String getFileSuffix(String filename) {
int index = filename.lastIndexOf(".");
if (index == -1) {
return "";
}
return filename.substring(index);
}
}

FileUtils

FileUtils
public class FileUtils {

/**
* response写入文件资源
*
* @param response response
* @param filePath 文件路径
*/
public static void writeImage(HttpServletResponse response, String filePath) {
if (!StringUtils.hasText(filePath)) {
return;
}
String imageSuffix = StringTools.getFileSuffix(filePath);
imageSuffix = imageSuffix.replace(".", "");
String contentType = "image/" + imageSuffix;
response.setContentType(contentType);
response.setHeader("Cache-Control", "max-age=2592000");
FileUtil.readFile(response, filePath);
}

/**
* 写入下载文件
*
* @param response response
* @param request request
* @param filename 文件名
* @param filePath 文件路径
*/
public static void writeDownloadFile(HttpServletResponse response, HttpServletRequest request, String filename, String filePath) throws UnsupportedEncodingException {
response.setContentType("application/x-msdownload; character=UTF-8");
if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") > 0) {
// IE浏览器
filename = URLEncoder.encode(filename, "UTF-8");
} else {
filename = new String(filename.getBytes("UTF-8"), "ISO8859-1");
}
response.setHeader("Content-Disposition", "attachment;filename=\"" + filename + "\"");
FileUtil.readFile(response, filePath);
}
}