--- title: SpringBoot Common date: 2025-12-15 --- # 一、框架说明   本框架基于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 dependencies starters framework autoconfigure starter-parent ``` ## 3.2 dependencies模块   该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过``管理,只声明依赖的版本,并不会实际引入依赖。   后续有新增依赖时,需要先在dependencies模块声明版本,然后在相应的starter模块中增加依赖。 ```xml 17 ${java.version} ${java.version} 3.5.0 2.7.0 1.8.8 2.7.0 3.0.4 3.5.12 1.43.0 5.8.26 2.19.0 4.4 3.14.0 3.9.0 31.1-jre 1.5.5.Final 1.0.6 1.18.30 0.2.0 3.11.0 0.41.0 0.10.2 org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc.version} com.sndyuk logback-more-appenders ${logback-more-appenders.version} org.komamitsu fluency-fluentd ${fluency-fluentd.version} cn.dev33 sa-token-bom ${sa.token.version} pom import cn.hutool hutool-bom ${hutool.version} pom import org.mybatis mybatis-spring ${mybatis-spring.version} com.baomidou mybatis-plus-bom ${mybatis.plus.version} pom import commons-io commons-io ${commons.io.version} org.apache.commons commons-collections4 ${commons.collections.version} org.apache.commons commons-lang3 ${commons-lang3.version} commons-net commons-net ${commons.net.version} com.google.guava guava-bom ${guava.version} pom import org.mapstruct mapstruct ${mapstruct.version} com.github.yitter yitter-idgenerator ${yitter.idgenerator.version} com.cxx starter-logging 2.0.0 com.cxx starter-web 2.0.0 com.cxx starter-jdbc 2.0.0 org.springframework.boot spring-boot-maven-plugin ${spring.boot.version} org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} io.fabric8 docker-maven-plugin ${docker-maven-plugin.version} org.graalvm.buildtools native-maven-plugin ${native-maven-plugin.version} ``` ::: 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(" Request URL {} | Time Taken {} ms", request.getRequestURI(), duration); } } /** * 处理审计包请求 * @param request 请求 */ private void handleAuditPackageRequest(HttpServletRequest request) { if (StringUtils.isEmpty(request.getQueryString())) { logger.info(" {} {}", request.getMethod(), request.getRequestURI()); } else { logger.info(" {} {}?{}", 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 { @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> 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> converterType) throws IOException { return inputMessage; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { // 入参结束 try { String jsonBody = objectMapper.writeValueAsString(body); logger.info(" {}", jsonBody); } catch (JsonProcessingException e) { logger.info(" {}", body); } return body; } @Override public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { return body; } @Override public boolean supports(MethodParameter returnType, Class> 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> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 出参结束 if (body != null && !(body instanceof ErrorResponse)) { try { String jsonBody = objectMapper.writeValueAsString(body); logger.info(" {}", jsonBody); } catch (JsonProcessingException e) { logger.info(" {}", 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 gitee Cxx0822 token gitee central https://maven.aliyun.com/repository/central true true gitee https://gitee.com/Cxx0822/springboot2-common/raw/master/repo central https://maven.aliyun.com/repository/central true true gitee ```   注:这里要去掉mirror的阿里云镜像。 3. 在工程的根目录的pom.xml中添加发布配置: ```xml gitee springboot2-common file:D:/temp/maven ```   注:gitee不支持通过deploy发布jar包,可以先发布到本地,再将文件复制到项目文件夹下,通过git push推送。 4. 将本地产生的发布文件上传至git仓库中。 5. 其他项目引用: ```xml com.cxx starter-web 1.0.0 com.cxx starter-jdbc 1.0.0 com.cxx starter-logging 1.0.0 ``` # 五、项目使用 1. 将父工程改为starter-parent模块 ```xml com.cxx starter-parent 2.0.0 ``` 2. 根据需要引入starter-web、starter-jdbc和starter-logging模块 ```xml com.cxx starter-web 2.0.0 com.cxx starter-jdbc 2.0.0 com.cxx starter-logging 2.0.0 ``` 3. 打包模块时,需要添加spring-boot-maven-plugin ```xml org.springframework.boot spring-boot-maven-plugin ```