SpringBoot下构建良好的后端开发规范

前言

本文将从最基础的Java SpringBoot环境,编写一个最基础的接口。

比在此基础上逐渐延申,最终形成一套标准的企业级后端接口开发规范。

1.环境搭建

1.1创建Maven工程

1.2引入所需依赖

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qilixiang</groupId>
    <artifactId>backend_api</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- SpringBoot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- SpringBoot Web模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- Test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <!-- hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.22</version>
        </dependency>
        <!-- AspectJ支持-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <!-- Mybatis-Plus启动器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- mysql连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <!-- 自定义校验注解-->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.16.Final</version>
            <exclusions>
                <exclusion>
                    <artifactId>validation-api</artifactId>
                    <groupId>javax.validation</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- swagger接口文档-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.3启动类

/**
 * @author com.qilixiang
 * @date 2022/12/26 15:50
 */
@SpringBootApplication
@Slf4j
@EnableScheduling
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

1.4配置文件

server:
  port: 8024
spring:
  application:
    name: api
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #    需要修改db连接
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root

1.5测试接口

@RestController
@RequestMapping("user")
public class UserController {

    @RequestMapping("/add")
    public String add() {
        return "SUCCESS";
    }
    
}

目录结构

启动项目,请求接口测试

localhost:8024/user/add

2.统一返回数据格式

一个项目内的所有接口,必须有统一的风格,统一返回格式。

其中,返回需要包括最主要的三部分:状态码、信息、数据。状态码是其中最重要的,需要明确,且根据场景去分配。不妨可以参考下一些第三方的api,无一例外都是契合这个标准的。

构建接口返回结果类

// 默认code 1=成功、0=失败,这里不建议像这样写死状态码,应该用枚举维护。
@Data
public class Result<T> implements Serializable {

    private int code = 1;
    private String message;
    private T data;

    public Result() {
    }

    public Result(int code) {
        this.code = code;
    }

    public Result(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        Result result = new Result();
        result.setData(data);
        return result;
    }

    public static <T> Result<T> error(int code, String message) {
        Result result = new Result(code, message);
        return result;
    }

    public static <T> Result<T> error(String message) {
        Result result = new Result(0, message);
        return result;
    }
}

改造测试接口

    @PostMapping("/add")
    public Result addUser() {
        return Result.success("SUCCESS");
    }

改造前

改造后

3.统一异常处理

为什么要配置统一异常处理

在用户体验方面来解释,代码中不可避免会触发一下bug,当bug发生时,给用户的反馈一般有以下两种情况。

对于用户来说,第一种反馈是十分不友好的,在用户的视角就是一堆乱码。第二种,起码能让用户知道,是服务器内部繁忙,就算其实不是。

配置全局异常处理

首先需要新建一个类,在这个类上加上 @ControllerAdvice/@RestControllerAdvice注解 , 这个类就配置成全局处理类了。然后在类中新建一系列方法,在方法上加上 @ExceptionHandler注解 并指定想处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获全局所有异常
     */
    @ExceptionHandler(value = Exception.class)
    public Result exceptionHandler(Exception e) {
        log.error("出现未知异常 -> ", e);
        return Result.error(e.getMessage());
    }
}

测试接口

    @RequestMapping("/test")
    public void test() {
        int i = 1 / 0;
    }

更多异常捕获

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获全局异常,处理所有不可知的异常
     */
    @ExceptionHandler(value = Exception.class)
    public Result exceptionHandler(Exception e) {
        log.error("出现未知异常 -> ", e);
        return Result.error(e.getMessage());
    }

    /**
     * 捕获空指针
     */
    @ExceptionHandler(value = NullPointerException.class)
    public Result npeExceptionHandler(NullPointerException e) {
        log.error("空指针 -> ", e);
        return Result.error(e.getMessage());
    }

    /**
     * 参数校验异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result ArgumentValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return Result.error(objectError.getDefaultMessage());
    }

    // 下面添加更多异常捕获
}

4.参数校验

任何、所有、每一个接口必须对参数进行安全校验!!

最简单常见做法,是把将逻辑写在业务代码层里。

    /**
     * 添加用户
     *
     * @param user 用户数据
     * @return
     */
    public boolean add(User user) {
        if (user == null) {
            log.info("对象不能为空");
            return false;
        }
        if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) {
            log.info("不能输入空字符串");
            return false;
        }
        if (user.getPassword().length() < 8 || user.getPassword().length() > 12) {
            log.info("密码长度必须是8-12个字符");
            return false;
        }
        if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) {
            log.info("邮箱格式不正确");
            return false;
        }
        // 参数校验通过,这里写上业务逻辑
        return true;
    }

这样做有以下不好的地方

  1. 代码冗长,业务不够清晰
  2. 后续还有很多校验,又得再写一套,复用性不高

Validator参数校验

在实体类(参数)内部定义校验规则+信息,接收到参数后自动根据事先定义好的规格,校验是否通过。

改造前

@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String email;
    private Integer age;
}

改造后

@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Long id;

    private String username;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 8, max = 12, message = "密码长度必须是8-12个字符")
    private String password;

    @NotNull(message = "用户邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    private Integer age;
}

在接口需要校验的参数上加上@Valid注解即可。因为已经提前配置了全局异常处理,校验失败的message也能有统一的格式返回。

    @ApiOperation("添加用户")
    @PostMapping("/add")
    public Boolean addUser(@RequestBody @Valid User user) {
        return userService.add(user);
    }

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RlixGSE-1672040959439)(SpringBoot后端接口.assets/image-20221226141119219.png)]

5.自定义异常

有时候,有一些异常是我们需要手动抛出的。比如在业务中,有些情况不符合业务逻辑,这时候可以选择手动抛出异常,中止继续操作,或回滚数据,其中,就可以加入我们自定义的异常。

我们现在就来开始写一个自定义异常

/**
 * 自定义异常,需要继承runtimeException
 *
 * @author qilixiang
 * @date 2022/12/26 14:15
 */
@Getter
public class CommonException extends RuntimeException {

    private int code;
    private String msg;

    public CommonException() {
        this(0, "接口异常");
    }

    public CommonException(String msg) {
        this(0, msg);
    }

    public CommonException(int code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}

统一异常处理捕获此自定义异常

    @ExceptionHandler(CommonException.class)
    public Result APIExceptionHandler(CommonException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

测试

    @RequestMapping("/test")
    public void test() {
        throw new CommonException("自定义异常");
    }

6.统一日志处理

SpringBoot已经对logback做了集成,只需简单配置即可。

logback-spring.xml(resource目录下)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
                 当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
    <contextName>logback</contextName>

    <springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
    <property name="LOG_HOME" value="logs"/>
    <property name="LOG_PATTERN"
              value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!--1. 输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最低级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 2. 输出到文件  -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 当前记录的日志文档完整路径 -->
        <file>${LOG_HOME}/all.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} -%5level ---[%15.15thread] %-40.40logger{39} : %msg%n%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数(非持续运行情况下,15天外不会清理)-->
            <maxHistory>15</maxHistory>
            <!-- 应对服务非持续偶尔运行,日志清理机制无法触发而导致日志得不到清理的情况-->
            <totalSizeCap>500MB</totalSizeCap>
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的 addtivity 属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--
        root节点是必选节点,用来指定通用的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->
    <!-- cloud环境下,去掉nacos的日志打印信息 -->
    <logger name="com.alibaba.nacos" level="OFF" addtivity="false"></logger>

    <logger name="com.qilixiang" level="INFO"/>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

    <!-- 本地环境输出至控制台 -->
    <!-- 如果使用了 springProfile, 需要将logback.xml名称改为logback-spring.xml-->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

</configuration>

配置文件

logging:
  config: classpath:logback-spring.xml

会在项目根目录下生成日志文件夹

7.构建swagger接口文档

配置后开启即可

配置类

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${swagger.enable}")
    private boolean enable;

    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(enable)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.qilixiang"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 文档信息
     *
     * @return
     */
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("标题")
                .description("描述")
                .termsOfServiceUrl("qilixiang")
                .contact(new Contact("联系人", "", "邮箱"))
                .version("1.0")
                .build();
    }
}

application.yml

swagger:
  enable: true #是否启用swagger

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6NRdUtld-1672040959440)(SpringBoot后端接口.assets/image-20221226151823144.png)]

启动项目,访问 项目地址:端口/doc.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MVGRgF0F-1672040959441)(SpringBoot后端接口.assets/image-20221226151931838.png)]

最终的项目接口

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为一个Spring Boot后端工程师,你需要掌握以下技术: 1. Spring框架:Spring Boot是基于Spring框架的,因此你需要熟悉Spring框架的核心概念和功能,包括IoC(控制反转)、AOP(面向切面编程)、Spring MVC等。 2. 数据访问:Spring Boot提供了对数据访问的支持,你需要了解Spring Data JPA、MyBatis等ORM框架,以及常见的数据库操作技术。 3. RESTful API设计:Spring Boot常用于构建Web服务,你需要了解RESTful API的设计原则和规范,并使用Spring MVC构建可扩展和易于维护的API。 4. 安全性:你需要了解Spring Security框架,以实现身份认证和授权管理,保护你的应用程序免受潜在的安全威胁。 5. 缓存和消息队列:Spring Boot集成了多种缓存和消息队列框架,如Redis、RabbitMQ、Kafka等,你需要了解它们的用法和配置。 6. 微服务架构:Spring Boot可以用于构建微服务架构,你需要了解微服务的概念和原则,以及使用Spring Cloud等相关工具来实现微服务的注册、发现和通信。 7. 日志和监控:你需要使用Spring Boot的日志框架(如Logback、Log4j)记录应用程序的日志信息,并使用监控工具(如Spring Boot Actuator)监控应用程序的运行状态。 此外,你还需要熟悉一些常见的开发工具,如Maven或Gradle进行项目构建和依赖管理,以及使用Git进行版本控制。掌握这些技术将使你能够构建高效、可靠和安全的Spring Boot后端应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值