后端采用 SpringBoot 搭建项目,开发工具使用IDEA,为了简化开发,建议安装 Lombok 插件。
关于项目中类名以及包名的命名方式参考这篇文章:告别编码5分钟,命名2小时!史上最全的Java命名规范参考!
1、新建maven工程,导入依赖
<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.0modelVersion>
<groupId>pers.quanyucc.qbloggroupId>
<artifactId>qblog-serverartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.1.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.1.6.RELEASEversion>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>4.6.1version>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
<version>2.0.2version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
依赖解释:
至于为什么要使用 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
配置说明:
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", "[email protected]"))
.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();
}
}
注解说明:
以上使用到了的自定义工具类 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
接口文档的主界面如下:
查看接口信息:
进行接口测试:
当我们试图访问不存在的 api 时,会出现如下页面,但是我们希望返回的是我们自定义的 json 字符串
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);
}
}
注解说明:
5、此时再进行访问,可以发现返回值为我们自定的 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