869 lines
28 KiB
Markdown
869 lines
28 KiB
Markdown
# 一、框架说明
|
||
  本框架基于Spring Boot3框架二次开发,增加了依赖包管理和启动项配置等功能。
|
||
|
||
# 二、项目结构
|
||
|
||
| 项目模块 | 模块含义 | 主要功能 |
|
||
| :-----------: | :-------------------: | :---------------------------------: |
|
||
| starters | SpringBoot 启动项 | 主要包括web、jdbc和log启动项配置 |
|
||
| autoconfigure | staters具体启动配置类 | 主要包括web和jdbc具体的启动项配置类 |
|
||
| dependencies | 依赖项 | 主要包括本框架中的依赖包管理 |
|
||
| framework | 通用配置 | 主要包括web和data的一些通用工具方法 |
|
||
|
||
# 三、项目说明
|
||
## 3.1 common模块
|
||
  该模块主要声明项目结构,包括autoconfigure、dependencies、framework、starter-parent和starters等模块。
|
||
```xml
|
||
<modules>
|
||
<!-- modules表示聚合关系,即common有以下模块 -->
|
||
<module>dependencies</module>
|
||
<module>starters</module>
|
||
<module>framework</module>
|
||
<module>autoconfigure</module>
|
||
<module>starter-parent</module>
|
||
</modules>
|
||
```
|
||
|
||
## 3.2 dependencies模块
|
||
  该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过`<dependencyManagement>`管理,只声明依赖的版本,并不会实际引入依赖。
|
||
  后续有新增依赖时,需要先在dependencies模块声明版本,然后在相应的starter模块中增加依赖。
|
||
```xml
|
||
<properties>
|
||
<java.version>17</java.version>
|
||
|
||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||
|
||
<spring.boot.version>3.5.0</spring.boot.version>
|
||
<springdoc.version>2.7.0</springdoc.version>
|
||
|
||
<logback-more-appenders.version>1.8.8</logback-more-appenders.version>
|
||
<fluency-fluentd.version>2.7.0</fluency-fluentd.version>
|
||
|
||
<mybatis-spring.version>3.0.4</mybatis-spring.version>
|
||
<mybatis.plus.version>3.5.12</mybatis.plus.version>
|
||
<sa.token.version>1.43.0</sa.token.version>
|
||
<hutool.version>5.8.26</hutool.version>
|
||
|
||
<commons.io.version>2.19.0</commons.io.version>
|
||
<commons.collections.version>4.4</commons.collections.version>
|
||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||
<commons.net.version>3.9.0</commons.net.version>
|
||
<guava.version>31.1-jre</guava.version>
|
||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||
<yitter.idgenerator.version>1.0.6</yitter.idgenerator.version>
|
||
|
||
<lombok.version>1.18.30</lombok.version>
|
||
<lombok.mapstruct.version>0.2.0</lombok.mapstruct.version>
|
||
|
||
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||
<docker-maven-plugin.version>0.41.0</docker-maven-plugin.version>
|
||
<native-maven-plugin.version>0.10.2</native-maven-plugin.version>
|
||
</properties>
|
||
|
||
<!-- dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||
需要在具体的模块中显式声明依赖,才能让模块使用这些库 -->
|
||
<dependencyManagement>
|
||
<dependencies>
|
||
<!-- SpringBoot依赖 -->
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-dependencies</artifactId>
|
||
<version>${spring.boot.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
|
||
<!-- SpringDoc OpenAPI + Swagger UI -->
|
||
<dependency>
|
||
<groupId>org.springdoc</groupId>
|
||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||
<version>${springdoc.version}</version>
|
||
</dependency>
|
||
|
||
<!-- 扩展 logback appender -->
|
||
<dependency>
|
||
<groupId>com.sndyuk</groupId>
|
||
<artifactId>logback-more-appenders</artifactId>
|
||
<version>${logback-more-appenders.version}</version>
|
||
</dependency>
|
||
<!-- Fluentd 日志搜集和转发 -->
|
||
<dependency>
|
||
<groupId>org.komamitsu</groupId>
|
||
<artifactId>fluency-fluentd</artifactId>
|
||
<version>${fluency-fluentd.version}</version>
|
||
</dependency>
|
||
|
||
<!-- SaToken BOM -->
|
||
<dependency>
|
||
<groupId>cn.dev33</groupId>
|
||
<artifactId>sa-token-bom</artifactId>
|
||
<version>${sa.token.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
|
||
<!-- hutool BOM -->
|
||
<dependency>
|
||
<groupId>cn.hutool</groupId>
|
||
<artifactId>hutool-bom</artifactId>
|
||
<version>${hutool.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
|
||
<!-- MyBatis -->
|
||
<dependency>
|
||
<groupId>org.mybatis</groupId>
|
||
<artifactId>mybatis-spring</artifactId>
|
||
<version>${mybatis-spring.version}</version>
|
||
</dependency>
|
||
|
||
<!-- MyBatis-Plus Maven BOM -->
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-bom</artifactId>
|
||
<version>${mybatis.plus.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
|
||
<!-- IO工具类 -->
|
||
<dependency>
|
||
<groupId>commons-io</groupId>
|
||
<artifactId>commons-io</artifactId>
|
||
<version>${commons.io.version}</version>
|
||
</dependency>
|
||
<!-- 集合工具类 -->
|
||
<dependency>
|
||
<groupId>org.apache.commons</groupId>
|
||
<artifactId>commons-collections4</artifactId>
|
||
<version>${commons.collections.version}</version>
|
||
</dependency>
|
||
<!-- 字符串工具类 -->
|
||
<dependency>
|
||
<groupId>org.apache.commons</groupId>
|
||
<artifactId>commons-lang3</artifactId>
|
||
<version>${commons-lang3.version}</version>
|
||
</dependency>
|
||
<!-- 网络工具类 -->
|
||
<dependency>
|
||
<groupId>commons-net</groupId>
|
||
<artifactId>commons-net</artifactId>
|
||
<version>${commons.net.version}</version>
|
||
</dependency>
|
||
<!-- 工具类(集合、缓存、并发、IO、字符串) -->
|
||
<dependency>
|
||
<groupId>com.google.guava</groupId>
|
||
<artifactId>guava-bom</artifactId>
|
||
<version>${guava.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
|
||
<!-- 实体映射工具类 -->
|
||
<dependency>
|
||
<groupId>org.mapstruct</groupId>
|
||
<artifactId>mapstruct</artifactId>
|
||
<version>${mapstruct.version}</version>
|
||
</dependency>
|
||
|
||
<!-- 雪花ID生成器 -->
|
||
<dependency>
|
||
<groupId>com.github.yitter</groupId>
|
||
<artifactId>yitter-idgenerator</artifactId>
|
||
<version>${yitter.idgenerator.version}</version>
|
||
</dependency>
|
||
|
||
<!-- starter中的依赖 -->
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-logging</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-web</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-jdbc</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
|
||
<build>
|
||
<pluginManagement>
|
||
<plugins>
|
||
<plugin>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||
<version>${spring.boot.version}</version>
|
||
</plugin>
|
||
<plugin>
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
<artifactId>maven-compiler-plugin</artifactId>
|
||
<version>${maven-compiler-plugin.version}</version>
|
||
</plugin>
|
||
<plugin>
|
||
<groupId>io.fabric8</groupId>
|
||
<artifactId>docker-maven-plugin</artifactId>
|
||
<version>${docker-maven-plugin.version}</version>
|
||
</plugin>
|
||
<plugin>
|
||
<groupId>org.graalvm.buildtools</groupId>
|
||
<artifactId>native-maven-plugin</artifactId>
|
||
<version>${native-maven-plugin.version}</version>
|
||
</plugin>
|
||
</plugins>
|
||
</pluginManagement>
|
||
</build>
|
||
```
|
||
|
||
::: tip
|
||
dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||
需要在具体的模块中显式声明依赖,才能让模块使用这些库
|
||
:::
|
||
|
||
## 3.3 framework模块
|
||
  该模块主要是一些通用的配置。
|
||
|
||
### 3.3.1 data
|
||
  主要提供了2个数据库实体类的基类。
|
||
::: code-group
|
||
```java [AbstractIdEntity]
|
||
@Getter
|
||
@Setter
|
||
public abstract class AbstractIdEntity {
|
||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||
private Long id;
|
||
}
|
||
```
|
||
|
||
```java [AbstractEntity]
|
||
@Getter
|
||
@Setter
|
||
public abstract class AbstractEntity extends AbstractIdEntity {
|
||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||
private LocalDateTime createTime;
|
||
|
||
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||
private String createBy;
|
||
|
||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||
private LocalDateTime updateTime;
|
||
|
||
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||
private String updateBy;
|
||
}
|
||
```
|
||
:::
|
||
|
||
### 3.3.2 web
|
||
  主要提供了自定义异常处理类。
|
||
::: code-group
|
||
```java [ErrorResponse]
|
||
/**
|
||
* 错误响应
|
||
*/
|
||
public final class ErrorResponse {
|
||
/**
|
||
* 自定义code
|
||
*/
|
||
private final String code;
|
||
|
||
/**
|
||
* 自定义消息
|
||
*/
|
||
private final String message;
|
||
|
||
public ErrorResponse(String message) {
|
||
this.code = "Unspecified";
|
||
this.message = message;
|
||
}
|
||
|
||
public ErrorResponse(String code, String message) {
|
||
this.code = code;
|
||
this.message = message;
|
||
}
|
||
|
||
public String getCode() {
|
||
return code;
|
||
}
|
||
|
||
public String getMessage() {
|
||
return message;
|
||
}
|
||
}
|
||
```
|
||
|
||
```java [CustomException]
|
||
/**
|
||
* 自定义异常类
|
||
*/
|
||
public class CustomException extends AbstractException {
|
||
|
||
public CustomException(String errorMessage) {
|
||
super(errorMessage);
|
||
}
|
||
|
||
public CustomException(String code, String errorMessage) {
|
||
super(code, errorMessage);
|
||
}
|
||
|
||
public CustomException(String errorMessage, Exception innerException) {
|
||
super(errorMessage, innerException);
|
||
}
|
||
|
||
public CustomException(String code, String errorMessage, Exception innerException) {
|
||
super(code, errorMessage, innerException);
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
```java [AbstractException]
|
||
/**
|
||
* 抽象异常类
|
||
*/
|
||
public abstract class AbstractException extends RuntimeException {
|
||
protected String code;
|
||
|
||
public AbstractException(String errorMessage) {
|
||
super(errorMessage);
|
||
}
|
||
|
||
public AbstractException(String code, String errorMessage) {
|
||
super(errorMessage);
|
||
this.code = code;
|
||
}
|
||
|
||
public AbstractException(String errorMessage, Exception innerException) {
|
||
super(errorMessage, innerException);
|
||
}
|
||
|
||
public AbstractException(String code, String errorMessage, Exception innerException) {
|
||
super(errorMessage, innerException);
|
||
this.code = code;
|
||
}
|
||
|
||
public String getCode() {
|
||
return code;
|
||
}
|
||
|
||
public void setCode(String code) {
|
||
this.code = code;
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
### 3.4 starters模块
|
||
  该模块为整个项目的核心模块,在实际项目中,通过引入相应的starter模块,并配合autoconfigure模块中的功能,即可实现快速自动装配。
|
||
  该模块的pom.xml声明的依赖会注入到实际的项目中。
|
||
  在springboot3中,可以在**main->resources->META-INF->spring->org.springframework.boot.autoconfigure.AutoConfiguration.imports**中声明需要自动执行的类。
|
||
|
||
#### 3.4.1 stater-jdbc
|
||
  该模块主要提供数据库相关的配置功能。
|
||
  目前实现的功能有:
|
||
1. 配置MybatisPlus拦截器,添加乐观锁和分页插件。
|
||
2. 自定义MybatisPlus ID生成器(雪花ID)。
|
||
3. 配置MybatisPlus自动填充字段(create_time、create_by、update_time和update_by)。
|
||
|
||
::: code-group
|
||
```java [JdbcAutoConfiguration]
|
||
/**
|
||
* jdbc自动配置
|
||
*/
|
||
@Configuration(proxyBeanMethods = false)
|
||
@ConditionalOnClass(SqlSessionFactory.class)
|
||
@Import({YitterGenerator.class, MybatisMetaObjectHandler.class})
|
||
public class JdbcAutoConfiguration {
|
||
/**
|
||
* MybatisPlus拦截器
|
||
* @return 拦截器
|
||
*/
|
||
@Bean
|
||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||
// 乐观锁插件
|
||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||
// 分页插件
|
||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||
return interceptor;
|
||
}
|
||
}
|
||
```
|
||
|
||
```java [YitterGenerator]
|
||
/**
|
||
* 雪花id生成器
|
||
*/
|
||
public class YitterGenerator implements IdentifierGenerator {
|
||
|
||
@Override
|
||
public Number nextId(Object entity) {
|
||
return YitIdHelper.nextId();
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
```java [MybatisMetaObjectHandler]
|
||
/**
|
||
* Mybatis Plus自动填充
|
||
*/
|
||
public class MybatisMetaObjectHandler implements MetaObjectHandler {
|
||
@Override
|
||
public void insertFill(MetaObject metaObject) {
|
||
this.strictInsertFill(metaObject, Constants.CREATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||
this.strictInsertFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||
|
||
if (StpUtil.isLogin()) {
|
||
this.strictInsertFill(metaObject, Constants.CREATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||
this.strictInsertFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void updateFill(MetaObject metaObject) {
|
||
this.strictUpdateFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||
|
||
if (StpUtil.isLogin()) {
|
||
this.strictUpdateFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||
}
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
  `@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时,这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
|
||
  `@Import`用于导入其他配置类或组件到当前配置类中。
|
||
|
||
#### 3.4.2 starter-web
|
||
  该模块主要提供web相关的配置功能。
|
||
  目前实现的功能有:
|
||
1. 注册审计拦截器
|
||
2. 注册Sa-Token拦截器
|
||
3. 注册安全拦截器
|
||
4. 配置CORS跨越
|
||
5. 全局异常处理器
|
||
|
||
::: code-group
|
||
```java [ServerAutoConfiguration]
|
||
@Configuration()
|
||
@Import({DefaultExceptionAdvice.class, AuditBodyAdvice.class, CustomProperties.class})
|
||
public class ServerAutoConfiguration implements WebMvcConfigurer {
|
||
@Resource
|
||
private CustomProperties customProperties;
|
||
|
||
/**
|
||
* 注册拦截器 需要实现 WebMvcConfigurer 接口
|
||
* @param registry 注册器
|
||
*/
|
||
@Override
|
||
public void addInterceptors(InterceptorRegistry registry) {
|
||
// 注册安全拦截器
|
||
// registry.addInterceptor(new SecurityInterceptor());
|
||
// 注册审计拦截器
|
||
registry.addInterceptor(new AuditInterceptor(customProperties.getBasePackage()));
|
||
// 注册Sa-Token拦截器 登录校验
|
||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
|
||
.excludePathPatterns("/error", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**")
|
||
.excludePathPatterns("/files/**")
|
||
.excludePathPatterns("/doc.html", "/webjars/**");
|
||
}
|
||
|
||
/**
|
||
* 配置CORS跨越 需要实现 WebMvcConfigurer 接口
|
||
* @return 过滤器
|
||
*/
|
||
@Bean
|
||
@Order(-128)
|
||
public CorsFilter corsFilter() {
|
||
CorsConfiguration config = new CorsConfiguration();
|
||
// 允许所有来源
|
||
config.addAllowedOrigin("*");
|
||
// 允许所有请求头
|
||
config.addAllowedHeader("*");
|
||
// 允许所有请求方法
|
||
config.addAllowedMethod("*");
|
||
|
||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||
source.registerCorsConfiguration("/**", config);
|
||
return new CorsFilter(source);
|
||
}
|
||
}
|
||
```
|
||
|
||
```java [AuditInterceptor]
|
||
public class AuditInterceptor implements HandlerInterceptor {
|
||
private static final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
|
||
|
||
private final String basePackage;
|
||
|
||
public AuditInterceptor(String basePackage) {
|
||
this.basePackage = basePackage;
|
||
}
|
||
|
||
@Override
|
||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||
// 如果是Http请求
|
||
if (handler instanceof HandlerMethod hd) {
|
||
request.setAttribute(Constants.AUDIT_TIME_FLAG, System.currentTimeMillis());
|
||
// 判断该请求是否需要审计
|
||
if (WebUtils.checkIsAuditPackages(hd.getBeanType().getPackage(), basePackage)) {
|
||
handleAuditPackageRequest(request);
|
||
}
|
||
}
|
||
|
||
// true表示继续处理请求
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||
// 这里我们不需要处理时间计算,所有的计算在 afterCompletion 中完成
|
||
}
|
||
|
||
@Override
|
||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||
// 获取开始时间
|
||
Long startTime = (Long) request.getAttribute(Constants.AUDIT_TIME_FLAG);
|
||
if (startTime != null) {
|
||
long duration = System.currentTimeMillis() - startTime;
|
||
logger.info("<AuditSummary> Request URL {} | Time Taken {} ms", request.getRequestURI(), duration);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理审计包请求
|
||
* @param request 请求
|
||
*/
|
||
private void handleAuditPackageRequest(HttpServletRequest request) {
|
||
if (StringUtils.isEmpty(request.getQueryString())) {
|
||
logger.info("<AuditSummary> {} {}", request.getMethod(), request.getRequestURI());
|
||
} else {
|
||
logger.info("<AuditSummary> {} {}?{}", request.getMethod(), request.getRequestURI(), WebUtils.format2UTF8(request.getQueryString()));
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```java [DefaultExceptionAdvice]
|
||
/**
|
||
* 全局异常处理器
|
||
*/
|
||
@RestControllerAdvice
|
||
public class DefaultExceptionAdvice {
|
||
|
||
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionAdvice.class);
|
||
|
||
/**
|
||
* 处理SaToken权限错误
|
||
* @param e 权限异常
|
||
* @return 错误响应
|
||
*/
|
||
@ExceptionHandler(SaTokenException.class)
|
||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||
public ErrorResponse handleSaTokenException(SaTokenException e) {
|
||
ErrorResponse response = new ErrorResponse(String.valueOf(e.getCode()), e.getMessage());
|
||
logger.error("<{}> {}", response.getCode(), e.toString());
|
||
return response;
|
||
}
|
||
|
||
/**
|
||
* 处理参数校验错误
|
||
* @param e 校验异常
|
||
* @return 错误响应
|
||
*/
|
||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||
public ErrorResponse handleValidationException(MethodArgumentNotValidException e) {
|
||
ErrorResponse response = new ErrorResponse(WebUtils.formatValidationException(e.getBindingResult().getFieldErrors()));
|
||
logger.error("<{}> {}", response.getCode(), response.getMessage());
|
||
return response;
|
||
}
|
||
|
||
/**
|
||
* 处理自定义异常错误
|
||
* @param e 自定义异常
|
||
* @return 错误响应
|
||
*/
|
||
@ExceptionHandler(AbstractException.class)
|
||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||
public ErrorResponse handleAbstractException(AbstractException e) {
|
||
ErrorResponse response;
|
||
if (e.getCode() == null || e.getCode().isEmpty()) {
|
||
response = new ErrorResponse(e.getMessage());
|
||
} else {
|
||
response = new ErrorResponse(e.getCode(), e.getMessage());
|
||
}
|
||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||
return response;
|
||
}
|
||
|
||
/**
|
||
* 处理默认异常错误
|
||
* @param e 默认异常
|
||
* @return 错误响应
|
||
*/
|
||
@ExceptionHandler
|
||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||
public ErrorResponse handleDefaultException(Exception e) {
|
||
ErrorResponse response = new ErrorResponse(e.getMessage());
|
||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||
return response;
|
||
}
|
||
}
|
||
```
|
||
|
||
```java [AuditBodyAdvice]
|
||
@ControllerAdvice
|
||
@Import({CustomProperties.class})
|
||
public class AuditBodyAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
|
||
@Resource
|
||
private CustomProperties customProperties;
|
||
|
||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||
|
||
|
||
private static final Logger logger = LoggerFactory.getLogger(AuditBodyAdvice.class);
|
||
|
||
@Override
|
||
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||
// 判断是否为需要审计的包
|
||
String auditPackages = customProperties.getBasePackage();
|
||
return WebUtils.checkIsAuditPackages(methodParameter.getDeclaringClass().getPackage(), auditPackages)
|
||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||
}
|
||
|
||
@Override
|
||
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
|
||
return inputMessage;
|
||
}
|
||
|
||
@Override
|
||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||
// 入参结束
|
||
try {
|
||
String jsonBody = objectMapper.writeValueAsString(body);
|
||
logger.info("<AuditRequest> {}", jsonBody);
|
||
} catch (JsonProcessingException e) {
|
||
logger.info("<AuditResponse> {}", body);
|
||
}
|
||
return body;
|
||
}
|
||
|
||
@Override
|
||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||
return body;
|
||
}
|
||
|
||
@Override
|
||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||
// 判断是否为需要审计的包
|
||
String auditPackages = customProperties.getBasePackage();
|
||
return WebUtils.checkIsAuditPackages(returnType.getDeclaringClass().getPackage(), auditPackages)
|
||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||
}
|
||
|
||
@Override
|
||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||
// 出参结束
|
||
if (body != null && !(body instanceof ErrorResponse)) {
|
||
try {
|
||
String jsonBody = objectMapper.writeValueAsString(body);
|
||
logger.info("<AuditResponse> {}", jsonBody);
|
||
} catch (JsonProcessingException e) {
|
||
logger.info("<AuditResponse> {}", body);
|
||
}
|
||
}
|
||
return body;
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
```java [CustomProperties]
|
||
/**
|
||
* 用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||
*/
|
||
@ConfigurationProperties(prefix = "web-starter")
|
||
public class CustomProperties {
|
||
// yml中web-starter下的base-package字段值
|
||
private String basePackage = "";
|
||
|
||
public String getBasePackage() {
|
||
return basePackage;
|
||
}
|
||
|
||
public void setBasePackage(String basePackage) {
|
||
this.basePackage = basePackage;
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
  `@ConfigurationProperties`用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||
|
||
::: tip
|
||
SpringBoot MVC总体执行顺序:
|
||
1. 进入Tomcat容器
|
||
2. 进入Filter过滤器
|
||
3. 进入Servlet容器
|
||
4. 进入Interceptor拦截器
|
||
5. 进入Controller控制器
|
||
6. 进入AOP
|
||
:::
|
||
|
||
::: tip
|
||
请求和响应体拦截器
|
||
RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后:
|
||
1. preHandle():请求处理前
|
||
2. beforeBodyRead:请求体反序列化前
|
||
3. @RequestBody:Controller方法参数绑定
|
||
4. Controller:请求处理
|
||
5. beforeBodyWrite():响应体序列化之前
|
||
6. postHandle():请求处理后
|
||
7. 视图渲染
|
||
8. afterCompletion():请求结束
|
||
:::
|
||
|
||
::: tip
|
||
审计拦截器 可以实现请求日志打印等功能
|
||
HandlerInterceptor 拦截器执行顺序:
|
||
1. preHandle():请求处理前 按注册顺序依次执行。
|
||
2. Controller:请求处理 请求到达Controller并被处理。
|
||
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
|
||
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
|
||
:::
|
||
|
||
# 四、项目发布
|
||
|
||
## 4.1 发布到Git中
|
||
1. 在Gitee/Github中创建工程,需要在工程中创建一个文件夹,例如repo,后续发布的文件要放在该文件夹下。
|
||
2. 在maven的setting.xml中添加server和repository信息
|
||
```xml
|
||
<servers>
|
||
<server>
|
||
<id>gitee</id>
|
||
<username>Cxx0822</username>
|
||
<password>token</password>
|
||
</server>
|
||
</servers>
|
||
|
||
<profiles>
|
||
<profile>
|
||
<id>gitee</id>
|
||
<repositories>
|
||
<repository>
|
||
<id>central</id>
|
||
<url>https://maven.aliyun.com/repository/central</url>
|
||
<releases>
|
||
<enabled>true</enabled>
|
||
</releases>
|
||
<snapshots>
|
||
<enabled>true</enabled>
|
||
</snapshots>
|
||
</repository>
|
||
|
||
<repository>
|
||
<id>gitee</id>
|
||
<url>https://gitee.com/Cxx0822/springboot2-common/raw/master/repo</url>
|
||
</repository>
|
||
</repositories>
|
||
|
||
<pluginRepositories>
|
||
<pluginRepository>
|
||
<id>central</id>
|
||
<url>https://maven.aliyun.com/repository/central</url>
|
||
<releases>
|
||
<enabled>true</enabled>
|
||
</releases>
|
||
<snapshots>
|
||
<enabled>true</enabled>
|
||
</snapshots>
|
||
</pluginRepository>
|
||
</pluginRepositories>
|
||
</profile>
|
||
</profiles>
|
||
|
||
<activeProfiles>
|
||
<activeProfile>gitee</activeProfile>
|
||
</activeProfiles>
|
||
```
|
||
|
||
  注:这里要去掉mirror的阿里云镜像。
|
||
|
||
3. 在工程的根目录的pom.xml中添加发布配置:
|
||
```xml
|
||
<distributionManagement>
|
||
<repository>
|
||
<id>gitee</id>
|
||
<name>springboot2-common</name>
|
||
<url>file:D:/temp/maven</url>
|
||
</repository>
|
||
</distributionManagement>
|
||
```
|
||
|
||
  注:gitee不支持通过deploy发布jar包,可以先发布到本地,再将文件复制到项目文件夹下,通过git push推送。
|
||
|
||
4. 将本地产生的发布文件上传至git仓库中。
|
||
5. 其他项目引用:
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-web</artifactId>
|
||
<version>1.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-jdbc</artifactId>
|
||
<version>1.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-logging</artifactId>
|
||
<version>1.0.0</version>
|
||
</dependency>
|
||
```
|
||
|
||
# 五、项目使用
|
||
1. 将父工程改为starter-parent模块
|
||
```xml
|
||
<parent>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-parent</artifactId>
|
||
<version>2.0.0</version>
|
||
<relativePath/>
|
||
</parent>
|
||
```
|
||
2. 根据需要引入starter-web、starter-jdbc和starter-logging模块
|
||
```xml
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-web</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-jdbc</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.cxx</groupId>
|
||
<artifactId>starter-logging</artifactId>
|
||
<version>2.0.0</version>
|
||
</dependency>
|
||
</dependencies>
|
||
```
|
||
3. 打包模块时,需要添加spring-boot-maven-plugin
|
||
```xml
|
||
<plugin>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||
</plugin>
|
||
``` |