全栈开发之后端脚手架:SpringBoot 集成 MybatisPlus 代码生成,分页
public static <T> Result<T> error(String msg) {
CodeMsg codeMsg = new CodeMsg(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
return new Result<T>(codeMsg);
}
// Constructor
private Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Result(T data, CodeMsg codeMsg) {
this.data = data;
if (codeMsg != null) {
this.code = codeMsg.getCode();
this.msg = codeMsg.getMsg();
}
}
private Result(CodeMsg codeMsg) {
if (codeMsg != null) {
this.code = codeMsg.getCode();
this.msg = codeMsg.getMsg();
}
}
}
CodeMsg.java
@Getter
public class CodeMsg {
private int code;
private String msg;
// 通用的错误码
public static final CodeMsg SUCCESS =new CodeMsg(HttpStatus.OK.value(), "success");
public static final CodeMsg BAD_REQUEST = new CodeMsg(HttpStatus.BAD_REQUEST.value(), "请求无效");
public static final CodeMsg SERVER_ERROR = new CodeMsg(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务端异常");
public static final CodeMsg NO_HANDLER_FOUND = new CodeMsg(HttpStatus.NOT_FOUND.value(), "未找到对应资源");
public static final CodeMsg UNAUTHORIZED = new CodeMsg(HttpStatus.UNAUTHORIZED.value(), "未认证或登录状态过期");
public static final CodeMsg FORBIDDEN = new CodeMsg(HttpStatus.FORBIDDEN.value(), "未授权");
// 自定义错误码
public static final CodeMsg PARAMETER_ERROR = new CodeMsg(4000, "参数不正确!");
/用户相关:验证码/
public static final CodeMsg CAPTCHA_EXPIRED = new CodeMsg(4001, "验证码不存在或已过期");
public static final CodeMsg CAPTCHA_INVALID = new CodeMsg(4002, "验证码错误");
/用户相关:认证授权/
public static final CodeMsg BAD_CREDENTIAL = new CodeMsg(4003, "用户名或密码错误");
public static final CodeMsg ACCOUNT_NOT_FOUND = new CodeMsg(4004, "账号不存在");
public static final CodeMsg ACCOUNT_NOT_ACTIVATED = new CodeMsg(4005, "账号未激活");
// 限流
public static final CodeMsg RATE_LIMIT = new CodeMsg(4006,"达到阈值啦!");
// 熔断
pu 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 blic static final CodeMsg DEGRADE = new CodeMsg(4007,"熔断啦!");
public static CodeMsg error(String msg){
return new CodeMsg(HttpStatus.BAD_REQUEST.value(),msg);
}
public CodeMsg(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
[](()全局异常拦截
默认拦截所有异常(也可自定义异常进行封装),同样通过 RestControllerAdvice
注解,实现对异常响应的统一封装。
RestExceptionHandler.java
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<String> exception(Exception e) {
log.error("Global exception: {}", null == e.getMessage() ? e.toString() : e.getMessage(), e);
return Result.error(CodeMsg.SERVER_ERROR.getCode(), null == e.getMessage() ? e.toString() : e.getMessage());
}
}
[](()CRUD 的 Controller
@RestController
@RequestMapping("book")
@Api(tags = "测试 Controller")
public class BookController {
@Autowired
IBookService bookService;
@GetMapping("hello")
@ApiOperation("哈喽")
public String hello() {
return "hello everyone.";
}
@GetMapping("list")
public List<Book> list() {
return bookService.list();
}
@PostMapping("save")
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@GetMapping("detail/{id}")
public Result detail(@PathVariable long id) {
return Result.success(bookService.getById(id));
}
@GetMapping("error")
public Result error() {
int value = 8 / 0;
return Result.success(value);
}
@GetMapping("page")
public Result<IPage<Book>> page(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam Map<String, Object> params) {
QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("read_date", params.get("readDate"));
IPage<Book> list = bookService.page(new Page<>(page, size),queryWrapper);
return Result.success(list);
}
}
[](()Swagger3 接口文档
引入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
配置类
@Configuration
@EnableOpenApi
public class SwaggerConfig {
private static final String VERSION = "1.0.0";
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.heartsuit.readingnotes.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SpringBoot+Swgger3.0 后端服务接口文档")
.contact(new Contact("Heartsuit", "https://blog.csdn.net/u013810234", "454670286@qq.com"))
.description("基于 Swagger3.0 生成的接口文档")
.termsOfServiceUrl("https://blog.csdn.net/u013810234")
.license("The Apache License, Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version(VERSION)
.build();
}
}
在控制器以及接口上添加注解
@Api(tags = "测试 Controller")
@RestController
public class HelloController {
@GetMapping("hello")
@ApiOperation("哈喽")
public String hello() {
return "Hello SpringBoot with Swagger3.0";
}
}
启动服务,浏览器访问
没错,再没其他额外的注解了,直接启动服务,然后在浏览器访问即可。
Note:
Swagger2.x 的访问地址:http://localhost:8000/swagger-ui.html
Swagger3.0 的访问地址:http://localhost:8000/swagger-ui/index.html
控制生成文档的开关
实际中我们的接口文档只会在开发环境下使用,所以一般我们会在生产环境下关闭文档。
application.yml
spring:
profiles:
active: dev
application-dev.yml
springfox:
documentation:
enabled: true
application-prod.yml
springfox:
documentation:
enabled: false
[](()遇到的问题
问题 1:控制台打印
MyBatisPlus
的SQL
日志
解决方法:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
问题 2:
Long
类型的雪花算法ID
传到前端后精度丢失
解决方法:在后端 JSON
返回前统一将 Long
转为字符串。
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
问题 3:全局异常处理时多个异常处理器匹配顺序
解决方法:
如下,除了全局拦截的所有异常 Exception
之外,还有一个自定义的异常 CustomException
,那么,当出现 CustomException
时,当前两个异常该如何匹配呢?答案是子类异常处理器优先,即会被 customException
方法拦截,而不会被 exception
方法拦截。
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<String> exception(Exception e) {
log.error("Global exception: {}", null == e.getMessage() ? e.toString() : e.getMessage(), e);
return Result.error(CodeMsg.SERVER_ERROR.getCode(), null == e.getMessage() ? e.toString() : e.getMessage());
}
@ExceptionHandler(CustomException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<String> customException(CustomException e) {
log.error("Custom exception: {}", null == e.getMessage() ? e.toString() : e.getMessage(), e);
return Result.error(e.getCode(), null == e.getMessage() ? e.toString() : e.getMessage());
}
}
@Getter
public class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
public CustomException(CodeMsg codeMsg) {
super(codeMsg.getMsg());
this.code = codeMsg.getCode();
}
public CustomException(Integer code, String msg){
super(msg);
this.code = code;
}
}
问题 4:访问 Swagger 地址时报错:Unable to infer base url. This is common when using dynamic servlet registration or when the API is behind an API Gateway. The base url is the root of where all the swagger resources are served. For e.g. if the api is available at http://example.org/api/v2/api-docs then the base url is http://example.org/api/. Please enter the location manually
解决方法:
原因是我们使用 RestControllerAdvice
统一处理接口响应,导致给 Swagger 的返回值也包装了一层,最终在浏览器无法解析、渲染页面。
将 @RestControllerAdvice
改为: @RestControllerAdvice(basePackages = "com.heartsuit.*.controller")
即限制 RestControllerAdvice
的拦截范围,仅处理指定包下的接口响应。
[](()项目依赖
<properties>
<java.version>11</java.version>
<mybatisplus.version>3.3.1</mybatisplus.version>
<swagger.version>3.0.0</swagger.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
评论