如何搭建漂亮的SpringBoot脚手架?

674 阅读6分钟

系列文章目录

第一章 如何搭建漂亮的SpringBoot脚手架?

一、项目初始化

1. 使用 Spring Initializr

使用 Spring Initializr 快速构建 Spring Boot 项目,填写项目元数据(如 Group、Artifact、Name 等),添加必要的依赖(如 Spring Web、MyBatis-Plus、Lombok 等)。

  • 项目根目录/src/main/java:放置项目Java源代码
  • 项目根目录/src/main/resources:放置项目静态资源和配置文件
  • 项目根目录/src/test/java:放置项目测试用例代码

位于 /src/main/java 目录下的Java源代码的组织结构,这地方也只能给出一个常用的结构,毕竟不同项目和团队实践不一样,有少许区别,但整体安排应该差不多。如果是多模块的项目的话,下面的结构应该只对应其中一个模块,其他模块的代码组织也大致差不多。

二、项目结构优化

1. 模块化设计

根据项目需求,将代码分为不同的模块,如 entity 、controller 、service 、mapper、config、utils 等。这有助于代码的组织和后期的维护。

|__advice: 
     |___annotation:  // 自定义注解
     |___aspect:  // AOP切面代码
     |___exception:  // 自定义异常处理
|__config:  // 配置类 (例如 SecurityConfig)
|__constant:  // 常量、枚举等定义
     |___consist:  // 常量定义
     |___enums:  // 枚举定义
|__controller:  // 控制器代码
     |___security:  // 权限管理
     |___system:  // 系统管理
|__domain:  // 数据模型定义
     |___entity:  // 实体对象定义(与数据表内的字段一一对应)
     |___dto:  // 数据传输对象定义(entity与vo之间的桥梁)
     |___vo:  // 显示层对象定义(前端用户能看见的数据)
|__filter:  // 过滤器、拦截器相关的代码
|__mapper/repository: // 数据访问层 (DAO/Repository)
|__param:
     |___request:  // 业务请求处理
     |___reponse:  // 业务响应处理
     |___result:  // 统一响应结果集处理
|__service:  // 具体的业务逻辑代码(接口和实现分离)
     |___intf:  // 业务逻辑接口定义
     |___impl:  // 业务逻辑实际实现
|__utils:  // 工具类和辅助代码

然后接下来 /src/main/resources 目录,里面主要存放静态配置文件和页面静态资源等东西:

|__mapper:  // 存放mybatis-plus的XML映射文件(如果是mybatis-plus项目)
|__static:  // 存放网页静态资源,比如下面的js/css/img
   |___js:
   |___css:
   |___img:
   |___font:
   |___...
|__template: // 存放网页模板,比如thymeleaf/freemarker模板等
   |___header:
   |___sidebar:
   |___bottom:
   |___XXX.html:
   |___...
|__application.yml        // 基本配置文件
|__application-dev.yml    // 开发环境配置文件
|__application-test.yml   // 测试环境配置文件
|__application-prod.yml   // 生产环境配置文件

2. 统一异常处理

创建一个全局异常处理器和业务异常处理器,通过 @ControllerAdvice 和 @ExceptionHandler 注解捕获并处理各类异常,返回统一的错误格式给前端。

package com.patrick.blog.advice.exception;

import com.patrick.blog.constant.enums.http.HttpCodeEnum;
import com.patrick.blog.param.result.JsonResult;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.net.UnknownHostException;
import java.nio.file.AccessDeniedException;
import java.util.List;

/**
 * <p>
 * 全局异常信息处理类
 * </p>
 *
 * @author Patrick
 * @since 2025-1-1
 */
@Slf4j
@RestControllerAdvice
public class GlobalException {

    /**
     * 全局异常处理
     *
     * @param e 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(Exception.class)
    public JsonResult<?> globalException(Exception e) {
        log.error("全局异常 => 原因是: {}", e.getMessage());
        return JsonResult.fail(HttpCodeEnum.OPERATOR_IS_FAILED, e.getMessage());
    }
    
    /**
     * 业务异常处理
     *
     * @param businessException 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(BusinessException.class)
    public JsonResult<?> businessException(BusinessException businessException) {
        log.error("业务异常 => code: {}, 原因是: {}", businessException.getCode(), businessException.getMessage());
        return JsonResult.fail(businessException.getCode(), businessException.getMessage());
    }

    /**
     * 参数校验异常处理
     *
     * @param methodArgumentNotValidException 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public JsonResult<?> methodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
        List<ObjectError> errors = methodArgumentNotValidException.getBindingResult().getAllErrors();
        // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
        FieldError fieldError = (FieldError) errors.getFirst();
        log.error("参数校验异常 => 校验参数: {}, 原因是: {}", fieldError.getField(), fieldError.getDefaultMessage());
        return JsonResult.fail(methodArgumentNotValidException.getStatusCode().value(), fieldError.getDefaultMessage());
    }

    /**
     * 页面不存在异常处理
     *
     * @param httpServletRequest 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public JsonResult<Object> noHandlerFoundException(HttpServletRequest httpServletRequest) {
        log.error("请求地址错误异常 => 请求方式: {}, 请求地址: {}", httpServletRequest.getMethod(), httpServletRequest.getServletPath());
        return JsonResult.fail(HttpCodeEnum.PAGE_NOT_FOUND);
    }

    /**
     * 请求方式错误异常处理
     *
     * @param httpServletRequest 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public JsonResult<Object> httpRequestMethodNotSupportedException(HttpServletRequest httpServletRequest) {
        log.error("请求方式错误异常 => 请求方式: {}, 请求地址: {}", httpServletRequest.getMethod(), httpServletRequest.getServletPath());
        return JsonResult.fail(HttpCodeEnum.REQUEST_METHOD_ERROR);
    }

    /**
     * 未知主机异常
     *
     * @param unknownHostException 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(UnknownHostException.class)
    public JsonResult<?> unknownHostException(UnknownHostException unknownHostException) {
        log.error("未知主机异常 => 原因是: {}", unknownHostException.getMessage());
        return JsonResult.fail(HttpCodeEnum.UNKNOWN_ERROR);
    }

    /**
     * 无访问权限异常处理
     *
     * @param accessDeniedException 异常信息
     * @return 错误信息
     */
    @ExceptionHandler(AccessDeniedException.class)
    public JsonResult<?> accessDeniedException(AccessDeniedException accessDeniedException) {
        log.error("无访问权限错误 => 原因是: {}", accessDeniedException.getMessage());
        return JsonResult.fail(HttpCodeEnum.PERMISSION_NOT_DEFINED);
    }

}

2. 配置管理

使用 application.yml 或 application.properties 文件进行配置管理,区分开发、测试、生产环境。利用 Spring Boot 的 @ConfigurationProperties 注解绑定配置类,实现配置的自动注入。

server:
  port: 8080 # 微服务端口号为8080

spring:
  application:
    name: ****** #服务名

  profiles: # 环境设置 dev表示构建阶段,test表示测试阶段,prod表示发布阶段
    active: dev

  datasource: #数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/数据库?serverTimezone=Asia/Shanghai
    username: ******
    password: ******

  jackson: # 我们的时区是东八区,应该加8个小时,时区显示格式也需要改成我们想要的
    date-format: yyyy-MM-DD HH:mm:ss
    time-zone: GMT+8

mybatis-plus: # mybatis-plus配置
  mapper-locations: classpath:mapper/*.xml # sql语句文件

knife4j: # knife4j的增强配置,不需要增强可以不配
  enable: true    # 开启knife4j,无需添加@EnableKnife4j注解
  setting:
    language: zh_cn   #中文
  basic: # 开启Swagger的Basic认证功能,默认是false
    enable: true
    username: ******
    password: ******

三、美化与增强

1. 引入 Knife4j

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。在 pom.xml 中添加 Knife4j 依赖,配置 Knife4jConfig 类,即可在 http://localhost:8080/doc.html 访问 API 文档。

2. 控制台日志美化

使用 Logback 或 Log4j2 作为日志框架,配置日志格式,使其更加清晰易读。例如,使用带颜色的控制台输出,便于快速定位问题。

四、Java开发 专有名词解释

1. DO、DTO、VO 如何区分

当然,这地方估计有一个很多人都会纠结的关于 DTO、VO、DO 等数据模型定义的区分。 这在《阿里巴巴Java开发手册》中倒是做了一个所谓的严格区分,那本书上是这样去定义的:

  • POJO(Plain Ordinary Java Object): 在本规约中,POJO 专指只有 setter/getter/toString 的简单类,包括 DO/DTO/BO/VO 等。
  • DO(Data Object):专指数据库表一一对应的 POJO 类。此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
  • DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
  • BO(Business Object):业务对象。由Service层输出的封装业务逻辑的对象。
  • Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。
  • VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
  • AO(Application Object):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。

不得不说,看到这么多对象的定义,烦skr人了都。