简介

后端采用 SpringBoot 搭建项目,开发工具使用IDEA,为了简化开发,建议安装 Lombok 插件。

步骤

关于项目中类名以及包名的命名方式参考这篇文章:告别编码5分钟,命名2小时!史上最全的Java命名规范参考!

搭建web项目,集成knife4j

1、新建maven工程,导入依赖

<?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>pers.quanyucc.qblog</groupId>
    <artifactId>qblog-server</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.6.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- undertow -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- hutool工具集 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.1</version>
        </dependency>
        <!-- knife4j -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- maven打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

依赖解释:

  • SpringBoot 父项目依赖
  • SpringBoot 启动器
  • SpringBoot web 组件,使用 exclusions 排除 tomcat ,然后在下方引入 undertow 依赖,这样可以实现 SpringBoot 内嵌服务器的切换
  • lombok 是一个简化实体类的插件,要使用 lombok 的话,IDEA 需要安装 lombok 插件
  • hutool 是一个 java 工具集,包含了许多好用的 java 工具方法
  • knife4j 可以理解为升级版的 Swagger ,相比于 Swagger 界面更加美观,操作更加顺手。文档地址
  • Swagger 的使用可以参考这篇文章:http://www.iocoder.cn/Spring-Boot/Swagger/

至于为什么要使用 undertow 可以参考这篇文章 : 为什么很多SpringBoot开发者放弃了Tomcat,选择了Undertow

2、在 resource 目录下新建 application.yml 、 application-dev.yml 、 application-pro.yml ,分别表示总配置文件、开发环境的配置文件、生产环境的配置文件

server:
  port: 9000
  servlet:
    context-path: /api/v1
spring:
  profiles:
    active: dev

配置说明:

  • 项目端口为9000
  • 访问项目的地址开头为 /api/v1
  • 当前生效的配置文件为 application-dev.yml

3、新建 SwaggerConfig ,配置接口文档

Swagger官方文档:https://swagger.io/docs/

package pers.qianyucc.qblog.config;

import com.github.xiaoymin.knife4j.spring.annotations.*;
import org.springframework.context.annotation.*;
import org.springframework.core.env.*;
import springfox.bean.validators.configuration.*;
import springfox.documentation.builders.*;
import springfox.documentation.service.*;
import springfox.documentation.spi.*;
import springfox.documentation.spring.web.plugins.*;
import springfox.documentation.swagger2.annotations.*;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {
    @Bean
    public Docket docket(Environment environment) {
        // 如果在dev环境(开发环境)就开启Swagger
        boolean isDev = environment.acceptsProfiles(Profiles.of("dev"));
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("默认接口")
                .enable(isDev)
                .select()
                .apis(RequestHandlerSelectors.basePackage("pers.qianyucc.qblog.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 配置Swagger的ApiInfo
     *
     * @return API配置信息
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("浅语小栈后端接口文档")
                .description("通过此文档可以查看、测试后端api")
                .contact(new Contact("芊雨", "https://gitee.com/qianyucc", "1413979079@qq.com"))
                .version("v1.0.0")
                .build();
    }
}

注意,此时接口文档还不能被访问到,此时还需要配置静态资源处理:新建 config 包,在config包里新建 WebConfig 配置类

package pers.qianyucc.qblog.config;

import org.springframework.context.annotation.*;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    /**
     * 解决 swagger静态资源无法访问的问题
     *
     * @param registry 资源处理程序注册表
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

4、封装通用接口返回对象

package pers.qianyucc.qblog.model.vo;

import io.swagger.annotations.*;
import lombok.*;
import lombok.experimental.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.utils.*;

@Data
@Accessors(chain = true)
@Builder
@ApiModel("通用接口返回对象")
public class Results<T> {
    @ApiModelProperty(required = true, notes = "结果码", example = "0")
    private int code;
    @ApiModelProperty(required = true, notes = "返回信息", example = "操作成功")
    private String msg;
    @ApiModelProperty(required = true, notes = "返回数据", example = "{\"id\":2001}")
    private T data;
    @ApiModelProperty(required = true, notes = "时间戳", example = "2020-06-29 09:07:34")
    private String timestamp;

    public static <T> Results<T> ok(T data) {
        return Results.<T>builder()
                .msg("操作成功")
                .data(data)
                .timestamp(BlogUtils.now())
                .build();
    }

    public static Results fromErrorInfo(IErrorInfo errorInfo) {
        return Results.builder()
                .code(errorInfo.getCode())
                .msg(errorInfo.getMsg())
                .timestamp(BlogUtils.now())
                .build();
    }

    public static <T> Results<T> ok(String msg, T data) {
        return Results.<T>builder()
                .msg(msg)
                .data(data)
                .timestamp(BlogUtils.now())
                .build();
    }

    public static <T> Results<T> error(T data) {
        return Results.<T>builder()
                .code(5000)
                .msg("操作失败")
                .data(data)
                .timestamp(BlogUtils.now())
                .build();
    }

    public static <T> Results<T> error(String msg, T data) {
        return Results.<T>builder()
                .code(5000)
                .msg(msg)
                .data(data)
                .timestamp(BlogUtils.now())
                .build();
    }
}

注解说明:

  • @Data :为lombok注解,简化setter、getter、以及构造函数的书写
  • @Accessors(chain = true) :lombok注解,开启链式调用
  • @Builder :lombok注解,可以使用 Builder方式创建对象
  • @ApiModel : Swagger 注解,表示该类是一个接口模型
  • @ApiModelProperty : Swagger 注解,描述属性信息

以上使用到了的自定义工具类 BlogUtils :

package pers.qianyucc.qblog.utils;

import java.time.*;
import java.time.format.*;

public class BlogUtils {
    /**
     * 以 Java8 的方式获取当前时间字符串
     *
     * @return 当前时间格式化之后的字符串
     */
    public static String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}

5、新建controller包,在controller包下新建comm包,编写测试接口

package pers.qianyucc.qblog.controller;

import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
import pers.qianyucc.qblog.model.vo.*;

@Api("通用接口")
@RestController
public class CommController {
    @ApiOperation("检查服务端是否正常")
    @GetMapping("/ping")
    public Results ping() {
        return Results.ok("欢迎访问QBlog API", null);
    }
}

6、编写启动类

package pers.qianyucc.qblog;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class BlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
    }
}

7、启动项目,访问 http://localhost:9000/api/v1/doc.html

接口文档的主界面如下:

接口文档的主界面

查看接口信息:

接口信息

进行接口测试:

测试接口

全局异常处理

处理404页面

当我们试图访问不存在的 api 时,会出现如下页面,但是我们希望返回的是我们自定义的 json 字符串

white page

1、在 application.yml 中增加如下配置

spring:
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false

2、定义错误信息接口,以及错误信息枚举

package pers.qianyucc.qblog.model.enums;

public interface IErrorInfo {
    /**
     * 获取错误信息
     * @return 错误信息
     */
    String getMsg();

    /**
     * 获取错误码
     * @return 错误码
     */
    int getCode();
}
package pers.qianyucc.qblog.model.enums;

public enum ErrorInfoEnum implements IErrorInfo{
    SUCCESS(0, "操作成功"),
    MISSING_PARAMETERS(4004, "参数缺失"),
    UNKNOWN_ERROR(5000, "出现未知错误"),
    RESOURCES_NOT_FOUND(4003, "找不到相应资源");

    private int code;
    private String msg;

    @Override
    public String getMsg() {
        return msg;
    }

    ErrorInfoEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public int getCode() {
        return code;
    }
}

3、修改 Results 类,使其可以通过 ErrorInfoEnum 进行创建对象

package pers.qianyucc.qblog.model.vo;

import io.swagger.annotations.*;
import lombok.*;
import lombok.experimental.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.utils.*;

@Data
@Accessors(chain = true)
@Builder
@ApiModel("通用接口返回对象")
public class Results<T> {
    public static Results fromErrorInfo(IErrorInfo errorInfo) {
        return Results.builder()
                .code(errorInfo.getCode())
                .msg(errorInfo.getMsg())
                .timestamp(BlogUtils.now())
                .build();
    }
    /* 此处省略上面已经写过的内容...... */ 
}

4、进行全局异常处理

package pers.qianyucc.qblog.global;

import lombok.extern.slf4j.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.model.vo.*;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Results noHandlerFoundExceptionHandler(NoHandlerFoundException exception) {
        log.error("NoHandlerFoundException:{}", exception.getMessage());
        return Results.fromErrorInfo(ErrorInfoEnum.RESOURCES_NOT_FOUND);
    }

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public Results exceptionHandler(Exception exception) {
        exception.printStackTrace();
        log.error("Exception:{}", exception.getMessage());
        return Results.fromErrorInfo(ErrorInfoEnum.UNKNOWN_ERROR);
    }
}

注解说明:

  • @Slf4j :lombok 注解,自动注入log对象到容器,可以使用log对象打印日志
  • @ControllerAdvice : 表示这个类用于全局异常处理
  • @ExceptionHandler : 表示此类处理什么类型的异常
  • @ResponseStatus : 返回的http状态码

5、此时再进行访问,可以发现返回值为我们自定的 json

自定义 JSON 串

自定义异常处理

这里有两种思路,一种是定义一个父类异常,然后每种自定义异常都去继承这个类。处理全局异常的时候,只需要判断该异常是不是继承自父类异常就行,这样的话,每一种异常都要定义一个异常对象;第二种就是下面要介绍的方法,只定义一个异常,通过构造函数给异常对象的异常信息赋值。

1、首先自定义异常 BlogException ,来表示博客系统的异常

package pers.qianyucc.qblog.exception;

import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.model.vo.*;
import pers.qianyucc.qblog.utils.*;

public class BlogException extends RuntimeException {
    private final IErrorInfo errorInfo;

    public BlogException(IErrorInfo errorInfo) {
        this.errorInfo = errorInfo;
    }

    /**
     * 将异常转换为 ResultVO 对象返回给前端
     *
     * @return 封装了异常信息的 ResultVO 对象
     */
    public Results toResultVO() {
        return Results.fromErrorInfo(errorInfo);
    }
}

2、在全局异常处理中添加 BlogException

package pers.qianyucc.qblog.global;


import lombok.extern.slf4j.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.*;
import pers.qianyucc.qblog.exception.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.model.vo.*;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = BlogException.class)
    @ResponseStatus(HttpStatus.OK)
    public Results blogExceptionHandler(BlogException exception) {
        log.error("BlogException:{}", exception.getMessage());
        return exception.toResultVO();
    }
    /* 此处省略上面已经写过的内容...... */ 
}

3、修改 controller 测试,抛出自定义异常

package pers.qianyucc.qblog.controller;

import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
import pers.qianyucc.qblog.exception.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.model.vo.*;

@Api("通用接口")
@RestController
public class CommController {
    @ApiOperation("检查服务端是否正常")
    @GetMapping("/ping")
    public Results ping() {
        throw new BlogException(ErrorInfoEnum.MISSING_PARAMETERS);
    }
}

4、通过 Swagger 测试接口

测试自定义异常

项目结构

使用 tree /f 命令,可以查看目录树

E:\User\Desktop\QBlog2\qblog-server\src>tree /f
卷 个人文件 的文件夹 PATH 列表
卷序列号为 000C-C313
E:.
├─main
│  ├─java
│  │  └─pers
│  │      └─qianyucc
│  │          └─qblog
│  │              │  BlogApplication.java
│  │              │
│  │              ├─config
│  │              │      SwaggerConfig.java
│  │              │      WebConfig.java
│  │              │
│  │              ├─controller
│  │              │      CommController.java
│  │              │
│  │              ├─dao
│  │              ├─exception
│  │              │      BlogException.java
│  │              │
│  │              ├─global
│  │              │      GlobalExceptionHandler.java
│  │              │
│  │              ├─model
│  │              │  ├─dto
│  │              │  ├─entity
│  │              │  ├─enums
│  │              │  │      ErrorInfoEnum.java
│  │              │  │      IErrorInfo.java
│  │              │  │
│  │              │  └─vo
│  │              │          Results.java
│  │              │
│  │              ├─service
│  │              └─utils
│  │                      BlogUtils.java
│  │
│  └─resources
│          application-dev.yml
│          application-pro.yml
│          application.yml
│
└─test
    └─java

参考代码:https://gitee.com/qianyucc/QBlog2/tree/v-1.0

Logo

前往低代码交流专区

更多推荐