weblog项目开发记录--SpringBoot后端工程骨架

知识点查漏补缺

跟着犬小哈做项目实战时发现好多知识点都忘了,还有一些小的知识点可能之前没学过,记录下!顺带整理下开发流程。

完整项目学习见犬小哈实战专栏

SpringBoot后端工程骨架

搭建好的工程骨架中实现了很多基础功能,如日志配置、参数校验、自定义响应、全局异常管理、Knife4j、Jackson序列化配置等。熟悉这些功能组件可以再以后开发新的项目时作为模板显著提升开发效率!

1、多模块项目

1.1 parent标签

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><!-- Maven 从仓库中查找父项目--><relativePath/></parent>

parent 标签用于指定当前项目的父项目,这意味着当前项目会继承父项目的一些配置,例如插件版本、依赖版本、构建设置等。
1.2 modules标签

    <!--    子模块管理--><modules><!--   入口模块--><module>weblog-web</module><!-- 管理后台 --><module>weblog-module-admin</module><!-- 通用模块 --><module>weblog-module-common</module></modules>

modules标签用来模块管理

1.3 properties标签

    <properties><!-- 项目版本号--><revision>0.0.1-SNAPSHOT</revision><java.version>1.8</java.version><guava.version>31.1-jre</guava.version><commons-lang3.version>3.12.0</commons-lang3.version><jackson.verson>2.15.2</jackson.verson><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><!-- Maven 相关 --><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target></properties>

properties标签中可以定义项目中可重用的属性或变量

1.4 dependencyManagement标签

<dependencyManagement><dependencies><dependency><groupId>com.bijing</groupId><artifactId>weblog-module-admin</artifactId><version>${revision}</version></dependency>......<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.verson}</version></dependency></dependencies></dependencyManagement>

统一依赖管理,只是声明依赖,并不自动实现引入,只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项。

1.5 pluginManagement标签

<build><!-- 统一插件管理,用于管理 Maven 插件的版本和配置 --><pluginManagement><!-- 插件列表,包含了各个插件的配置 --><plugins><!-- 插件配置 --><plugin><!-- Spring Boot Maven 插件,用于构建和打包 Spring Boot 项目 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- 插件的配置信息 --><configuration><!-- 配置选项,用于定制插件的行为 --><!-- 排除特定的依赖,这里是排除 lombok --><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></pluginManagement></build>

在父项目中声明插件的版本,以确保子项目使用相同的插件版本。

2、开发环境和生产环境配置

在这里插入图片描述
application.yml:默认的主配置文件,用于存放通用配置信息

# 企业级项目开发中,一般项目默认会激活 dev 环境
spring:profiles:#默认激活 dev 环境active: dev

application-dev.yml:针对开发环境的配置文件
application-prod.yml:针对生产环境的配置文件

# 在生产环境中使用特定的日志配置
logging:config: classpath:logback-weblog.xml

3、日志配置

在web模块的 pom.xml 中加入 spring-boot-starter-web 依赖时,它会自动包含 Logback 相关依赖,无需额外添加。日志功能一般放在common模块中,还需要加入下面依赖:

		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- Jackson工具类 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>

3.1 logback日志

logback-weblog.xml配置文件如下(更多见博文):

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!-- JMX配置,用于连接和管理JMX工具 --><jmxConfigurator/><!-- 引入Spring Boot默认日志配置 --><include resource="org/springframework/boot/logging/logback/defaults.xml" /><!-- 定义应用名称 --><property scope="context" name="appName" value="weblog" /><!-- 自定义日志输出路径,以及日志名称前缀 --><property name="LOG_FILE" value="../../logs/${appName}.%d{yyyy-MM-dd}"/><property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/><!-- 按照每天生成日志文件的配置 --><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 日志文件输出的文件名格式 --><FileNamePattern>${LOG_FILE}-%i.log</FileNamePattern><!-- 日志文件保留天数 --><MaxHistory>30</MaxHistory><!-- 日志文件最大的大小,当达到这个大小后,会触发滚动 --><TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>10MB</maxFileSize></TimeBasedFileNamingAndTriggeringPolicy></rollingPolicy><!-- 配置日志格式 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${FILE_LOG_PATTERN}</pattern></encoder></appender><!-- dev环境配置,仅输出到控制台 --><springProfile name="dev"><include resource="org/springframework/boot/logging/logback/console-appender.xml" /><root level="info"><appender-ref ref="CONSOLE" /></root></springProfile><!-- prod环境配置,仅输出到文件中 --><springProfile name="prod"><include resource="org/springframework/boot/logging/logback/console-appender.xml" /><root level="INFO"><appender-ref ref="FILE" /></root></springProfile></configuration>

3.2 Spring Boot 自定义注解,实现 API 请求日志切面

3.2.1 自定义注解

一般四个步骤:

  1. 创建自定义注解: 这是定义自己的注解,可以在需要的地方标记,并可能带有一些属性。
package com.bijing.weblog.common.aspect;import java.lang.annotation.*;/*** @author 毕晶* @date 2024/2/3 20:50*/@Retention(RetentionPolicy.RUNTIME)//表示该注解在运行时保留,因此可以通过反射机制在运行时获取注解信息
@Target({ElementType.METHOD})//表示该注解仅能被应用在方法上。
@Documented//表示该注解将包含在 Javadoc 中
public @interface ApiOperationLog {/*** API功能描述* @return*/String description() default "";}
  1. 创建切面类(Aspect): 这是定义切面逻辑的地方。切面是使用注解的方法执行前后执行的代码块。
package com.bijing.weblog.common.aspect;import com.bijing.weblog.common.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;/*** @author 毕晶* @date 2024/2/3 21:02*/
@Aspect
@Component
@Slf4j
public class ApiOperationLogAspect {/*** 以自定义 @ApiOperationLog 注解为切点,凡是添加 @ApiOperationLog 的方法,都会执行环绕中的代码*/@Pointcut("@annotation(com.bijing.weblog.common.aspect.ApiOperationLog)")public void apiOperationLog() {}/*** 环绕** @param joinPoint* @return* @throws Throwable*/@Around("apiOperationLog()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {try {// 请求开始时间long startTime = System.currentTimeMillis();// MDC:诊断上下文映射,开发人员可以在 诊断上下文 中放置一些信息,如用户身份信息,关键参数,操作描述,环境信息,异常信息等,// 而后通过特定的 logback 组件去获取,或者MDC.get(key)MDC.put("traceId", UUID.randomUUID().toString());// 获取被请求的类和方法// joinPoint 包含了被拦截方法的信息,允许在拦截器中获取和控制被拦截方法的执行/*Signature: 通过 getSignature() 方法可以获取连接点的签名,即被拦截方法的方法签名。Args: 通过 getArgs() 方法可以获取方法的参数数组。Target Object: 通过 getTarget() 方法可以获取目标对象,即被拦截的对象实例。This Object: 通过 getThis() 方法可以获取代理对象,即当前执行的代理对象。*/String className = joinPoint.getTarget().getClass().getSimpleName();String methodName = joinPoint.getSignature().getName();// 请求入参Object[] args = joinPoint.getArgs();// 入参转 JSON 字符串// map(toJsonStr()):将流中的每个参数对象转换为相应的 JSON 字符串// collect(Collectors.joining(", ")):将流中的元素收集并连接成一个字符串,其中每个元素之间用逗号 , 分隔。String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));// 功能描述信息String description = getApiOperationLogDescription(joinPoint);// 打印请求相关参数log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ",description, argsJsonStr, className, methodName);// 执行切点方法,result放的是被拦截方法的返回值Object result = joinPoint.proceed();// 执行耗时long executionTime = System.currentTimeMillis() - startTime;// 打印出参等相关信息log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ",description, executionTime, JsonUtil.toJsonString(result));return result;} finally {MDC.clear();}}/*** 获取注解的描述信息** @param joinPoint* @return*/private String getApiOperationLogDescription(ProceedingJoinPoint joinPoint) {// 1. 从 ProceedingJoinPoint 获取 MethodSignature(方法签名信息)MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 2. 使用 MethodSignature 获取当前被注解的 MethodMethod method = signature.getMethod();// 3. 从 Method 中提取 LogExecution 注解ApiOperationLog apiOperationLog = method.getAnnotation(ApiOperationLog.class);// 4. 从 LogExecution 注解中获取 description 属性return apiOperationLog.description();}/*** 转 JSON 字符串** @return*/private Function<Object, String> toJsonStr() {return JsonUtil::toJsonString;}
}
  1. 在启动类 WeblogWebApplication 中,手动添加包扫描 @ComponentScan: 在多模块项目中,Spring Boot 默认的组件扫描可能不会扫描所有模块的包,因此可能需要手动指定要扫描的包。
package com.bijing.weblog.web;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;@SpringBootApplication
@ComponentScan({"com.bijing.weblog.*"})// 多模块项目中,必需手动指定扫描 com.bijing.weblog 包下面的所有类
public class WeblogWebApplication {public static void main(String[] args) {SpringApplication.run(WeblogWebApplication.class, args);}}
  1. 在使用注解的方法上添加注解: 在需要应用切面逻辑的方法上添加自定义注解,这样 AOP 将在这些方法上生效。

补充:SpringBoot的AOP是默认开启的,不需要加注解@EnableAspectJAutoProxy

3.2.2 拾遗

元注解说明:
@Retention(RetentionPolicy.RUNTIME): 这个元注解用于指定注解的保留策略,即注解在何时生效。RetentionPolicy.RUNTIME 表示该注解将在运行时保留,这意味着它可以通过反射在运行时被访问和解析。
@Target({ElementType.METHOD}): 这个元注解用于指定注解的目标元素,即可以在哪些地方使用这个注解。ElementType.METHOD 表示该注解只能用于方法上。这意味着您只能在方法上使用这个特定的注解。
@Documented: 这个元注解用于指定被注解的元素是否会出现在生成的Java文档中。如果一个注解使用了 @Documented,那么在生成文档时,被注解的元素及其注解信息会被包含在文档中。这可以帮助文档生成工具(如 JavaDoc)在生成文档时展示关于注解的信息。

aspectj 注解说明

在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:

@Aspect:声明该类为一个切面类;
@Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;

切点定义好后,就是围绕这个切点做文章了:
@Before: 在切点之前,织入相关代码;
@After: 在切点之后,织入相关代码;
@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
@Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;

4、参数校验

Spring Boot提供了强大的参数校验功能,它建立在Java Bean Validation规范(JSR 380)之上。

4.1 引入依赖

首先,需要在 weblog-web 模块中的 pom.xml 文件添加参数校验依赖:

        <!-- 参数校验依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

4.2 实体类参数校验

package com.bijing.weblog.web.model;import lombok.Data;import javax.validation.constraints.*;/*** @author 毕晶* @date 2024/2/3 23:40*/
@Data
public class User {// 用户名@NotBlank(message = "用户名不能为空")private String username;// 性别@NotNull(message = "性别不能为空")private Integer sex;// 年龄@NotNull(message = "年龄不能为空")@Min(value = 18, message = "年龄必须大于或等于 18")@Max(value = 120, message = "年龄必须小于或等于120")private Integer age;//邮箱@NotBlank(message = "邮箱不能为空")@Email(message = "邮箱格式不正确")private String email;
}

4.3 Controller 参数校验

每个字段的校验注解添加完成后,还需要在 controller 层进行捕获,并将错误信息返回。

package com.bijing.weblog.web.controller;import com.bijing.weblog.common.aspect.ApiOperationLog;
import com.bijing.weblog.web.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.stream.Collectors;/*** @author 毕晶* @date 2024/2/3 23:40*/
@RestController
@Slf4j
public class TestController {@PostMapping("/test")@ApiOperationLog(description = "测试接口")public ResponseEntity<String> test(@RequestBody @Validated User user, BindingResult bindingResult) {// 是否存在校验错误if (bindingResult.hasErrors()) {// 获取校验不通过字段的提示信息String errorMsg = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", "));return ResponseEntity.badRequest().body(errorMsg);}// 返参return ResponseEntity.ok("参数没有任何问题");}}

ResponseEntity: Spring Framework 提供的一个表示 HTTP 响应的类。它包装了响应的状态码、头部信息和响应体等信息。
@Validated: 告诉 Spring 需要对 User 对象执行校验;
BindingResult : 验证的结果对象,其中包含所有验证错误信息;

4.4 拾遗

1、以下是 JSR 380 中提供的主要验证注解及其描述:

注解描述
@NotNull验证对象值不应为 null。
@AssertTrue验证布尔值是否为 true。
@AssertFalse验证布尔值是否为 false。
@Min(value)验证数字是否不小于指定的最小值。
@Max(value)验证数字是否不大于指定的最大值。
@DecimalMin(value)验证数字值(可以是浮点数)是否不小于指定的最小值。
@DecimalMax(value)验证数字值(可以是浮点数)是否不大于指定的最大值。
@Positive验证数字值是否为正数。
@PositiveOrZero验证数字值是否为正数或零。
@Negative验证数字值是否为负数。
@NegativeOrZero验证数字值是否为负数或零。
@Size(min, max)验证元素的大小是否在给定的最小值和最大值之间。
@Digits(integer, fraction)验证数字是否在指定的位数范围内。
@Past验证日期或时间是否在当前时间之前。
@PastOrPresent验证日期或时间是否在当前时间或之前。
@Future验证日期或时间是否在当前时间之后。
@FutureOrPresent验证日期或时间是否在当前时间或之后。
@Pattern(regexp)验证字符串是否与给定的正则表达式匹配。
@NotEmpty验证元素不为 null,并且其大小/长度大于0。
@NotBlank验证字符串不为 null,且至少包含一个非空白字符。
@Email验证字符串是否符合有效的电子邮件格式。

除了上述的标准注解,JSR 380 也支持开发者定义和使用自己的自定义验证注解。此外,这个规范还提供了一系列的APIs和工具,用于执行验证和处理验证结果。大部分现代Java框架(如 Spring 和 Jakarta EE)都与 JSR 380 兼容,并支持其验证功能。

2、以下是 ResponseEntity 的一些主要用法:

方法描述
ok()创建一个状态码为 200 OK 的 ResponseEntity 对象。
ok(T body)创建一个状态码为 200 OK 的 ResponseEntity 对象,并设置响应体。
status(HttpStatus status)创建一个指定状态码的 ResponseEntity 对象。
status(int status)创建一个指定状态码的 ResponseEntity 对象。
headers(HttpHeaders headers)设置响应头部信息。
header(String headerName, String... headerValues)添加指定名称和值的响应头。
body(T body)设置响应体。
created(URI location)创建一个状态码为 201 Created 的 ResponseEntity 对象,并设置 Location 头部信息。
noContent()创建一个状态码为 204 No Content 的 ResponseEntity 对象。

4.5 查漏补缺

以下是 BindingResult 的一些主要用法和特点:

主要用法和特点描述
获取验证错误信息通过 bindingResult.getFieldErrors() 方法可以获取所有验证失败的字段信息,每个字段错误包含字段名、错误码和默认错误信息等。
验证错误判断使用 bindingResult.hasErrors() 方法来判断是否存在验证错误。如果存在验证错误,可以根据实际情况进行相应的处理。
默认错误信息如果验证失败,BindingResult 会默认将错误信息存储在 FieldError 中,可以通过 getDefaultMessage() 方法获取默认的错误信息。
全局错误除了字段级别的错误信息,BindingResult 还可以包含全局错误信息。通过 bindingResult.getGlobalErrors() 获取。

5、自定义响应工具类

在开发 RESTful API 时,为了保持响应结构的一致性,公司内部一般都有标准化的响应格式。

5.1 设计响应模型

5.1.1接口执行成功返参格式
{"success": true,"data": null
}
5.1.2接口执行异常返参格式
{"success": false,"errorCode": "10000""message": "用户名不能为空"
}

5.2创建响应参数工具类

可以在common模块的utils包中定义响应参数工具类:

package com.bijing.weblog.common.utils;import lombok.Data;import java.io.Serializable;/*** @author 毕晶* @date 2024/2/5 23:51*/
@Data
public class Response<T> implements Serializable {// 是否成功,默认为 trueprivate boolean success = true;// 响应消息private String message;// 异常码private String errorCode;// 响应数据private T data;// =================================== 成功响应 ===================================public static <T> Response<T> success() {Response<T> response = new Response<>();return response;}public static <T> Response<T> success(T data) {Response<T> response = new Response<>();response.setData(data);return response;}// =================================== 失败响应 ===================================public static <T> Response<T> fail() {Response<T> response = new Response<>();response.setSuccess(false);return response;}public static <T> Response<T> fail(String errorMessage) {Response<T> response = new Response<>();response.setSuccess(false);response.setMessage(errorMessage);return response;}public static <T> Response<T> fail(String errorCode, String errorMessage) {Response<T> response = new Response<>();response.setSuccess(false);response.setErrorCode(errorCode);response.setMessage(errorMessage);return response;}}

5.3 在控制器中使用

有了 Response 工具类,再配合 Spring Boot 的 @RestController 或者 @ResponseBody 注解, 就可以快速生成 JSON 格式的响应数据了。

	@PostMapping("/test")@ApiOperationLog(description = "测试接口")public Response test(@RequestBody @Validated User user, BindingResult bindingResult) {// 是否存在校验错误if (bindingResult.hasErrors()) {// 获取校验不通过字段的提示信息String errorMsg = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", "));return Response.fail(errorMsg);}// 返参return Response.success();}

补充: 在接口的返参中,有很多 null 值的字段也返回了,咋办?
只需在 applicaiton.yml 文件中对 jackson 添加相关配置即可。

  jackson:# 设置后台返参,若字段值为 null, 则不返回default-property-inclusion: non_null# 设置日期字段格式date-format: yyyy-MM-dd HH:mm:ss

6、全局异常管理

除了系统异常,很多时候我们还需要处理业务异常,比较推荐的做法是,将自定义业务异常整合到全局异常管理中,使其更加统一且易于维护。

6.1 自定义一个基础异常接口

创建一个 BaseExceptionInterface 基础异常接口,方便后面做拓展。

package com.bijing.weblog.common.exception;/*** @author 毕晶* @date 2024/2/6 15:04*/
public interface BaseExceptionInterface {String getErrorCode();String getErrorMessage();
}

6.2 自定义错误码枚举

package com.bijing.weblog.common.enums;import com.bijing.weblog.common.exception.BaseExceptionInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;/*** @author 毕晶* @date 2024/2/6 15:05* @description 自定义错误码枚举*/
@Getter
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {// ----------- 通用异常状态码 -----------SYSTEM_ERROR("10000", "出错啦,后台小哥正在努力修复中..."),// ----------- 业务异常状态码 -----------PRODUCT_NOT_FOUND("20000", "该产品不存在(测试使用)"),;// 异常码private final String errorCode;// 错误信息private final String errorMessage;}

6.3 自定义业务异常

package com.bijing.weblog.common.exception;import lombok.Getter;
import lombok.Setter;/*** @author 毕晶* @date 2024/2/6 15:10* @description 自定义业务异常*/
@Setter
@Getter
public class BizException extends RuntimeException {// 异常码private String errorCode;// 错误信息private String errorMessage;public BizException(BaseExceptionInterface baseExceptionInterface) {this.errorCode = baseExceptionInterface.getErrorCode();this.errorMessage = baseExceptionInterface.getErrorMessage();}
}

补充:为啥是继承RuntimeException类而不是去实现BaseExceptionInterface接口呢?

从设计的角度去考虑,BizException的本质是个运行时异常,实现BaseExceptionInterface接口是一种功能行为的实现,这一点在继承RuntimeException时可以自定义实现,因此就没有必要去实现BaseExceptionInterface接口了。当然,实现了BaseExceptionInterface接口代码上也没有啥影响。

6.4 参数校验异常

当捕获到MethodArgumentNotValidException异常后,我们可以通过全局异常处理器来捕获该异常,统一返回错误信息。

改造 GlobalExceptionHandler 类,添加 handleMethodArgumentNotValidException() 方法:

   /*** 捕获参数校验异常* @return*/@ExceptionHandler({MethodArgumentNotValidException.class})@ResponseBodypublic Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request,MethodArgumentNotValidException e) {// 参数错误异常码String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();// 获取 BindingResultBindingResult bindingResult = e.getBindingResult();StringBuilder sb = new StringBuilder();// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';Optional.ofNullable(bindingResult.getFieldErrors()).ifPresent(errors -> {errors.forEach(error ->sb.append(error.getField()).append(" ").append(error.getDefaultMessage()).append(", 当前值: '").append(error.getRejectedValue()).append("'; "));});// 错误信息String errorMessage = sb.toString();log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);return Response.fail(errorCode, errorMessage);}

在参数错误枚举中添加:

    PARAM_NOT_VALID("10001", "参数错误"),

补充:在Controller中,需要不加 BindingResult 参数才能直接捕获其他异常并返回参数校验的异常信息。

    @PostMapping("/test")@ApiOperationLog(description = "测试接口")
//    如果希望在发生验证错误时返回相应的错误信息,就需要加上 BindingResult 参数。
//    如果不需要处理验证错误,或者希望直接捕获其他异常并返回通用的错误信息,就可以不加 BindingResult 参数。public Response test(@RequestBody @Validated User user) {......}

7、整合 Knife4j

Knife4j 是一个为 Java 项目生成和管理 API 文档的工具。

7.1 整合 Knife4j

在父项目 weblog-springboot 中的 pom.xml 文件中,添加 Knife4j 依赖版本号:

	<!-- 版本号统一管理 --><properties><!-- 依赖包版本 -->省略...        <knife4j.version>4.3.0</knife4j.version></properties><!-- 统一依赖管理 --><dependencyManagement><dependencies>省略...        <!-- knife4j(API 文档工具) --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>${knife4j.version}</version></dependency></dependencies></dependencyManagement>

因为 admin 后台管理模块和博客前台模块都需要调试接口,所以,我们需要在 weblog-web 和 weblog-module-admin 两个模块中,都需要引入该依赖:

		<!-- knife4j --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId></dependency>

7.2 添加配置类

新建名为 Knife4jConfig 配置类:

package com.bijing.weblog.web.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;/*** @author 毕晶* @date 2024/2/7 16:22*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {@Bean("webApi")public Docket createApiDoc() {Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInfo())// 分组名称.groupName("Web 前台接口").select()// 这里指定 Controller 扫描包路径.apis(RequestHandlerSelectors.basePackage("com.bijing.weblog.web.controller")).paths(PathSelectors.any()).build();return docket;}/*** 构建 API 信息** @return*/private ApiInfo buildApiInfo() {return new ApiInfoBuilder().title("Weblog 博客前台接口文档") // 标题.description("Weblog 是一款由 Spring Boot + Vue 3.2 + Vite 4.3 开发的前后端分离博客。") // 描述.termsOfServiceUrl("https://www.bilog.com/") // API 服务条款.contact(new Contact("bijing", "https://www.blog.com", "1457808125@qq.com")) // 联系人.version("1.0") // 版本号.build();}
}

浏览器访问路径 http://localhost:8080/doc.html , 就可以看到 api 管理界面了
在这里插入图片描述

7.3 给 controller 添加 Swagger 相关注解

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

7.4 生产环境如何屏蔽 Knife4j

7.4.1 Spring Boot Profile 特性

Profile 是 Spring Boot 中的一项特性,允许你在不同环境中使用不同的配置。

@Profile 注解:可以在配置类上添加 @Profile 注解,来控制 Knife4j 是否生效 。只有当指定的 Profile 处于激活状态时,该配置类才会被创建和被使用。

@Configuration
@EnableSwagger2WebMvc//启用 Swagger2
@Profile("dev")// 只在 dev 环境中开启
public class Knife4jConfig {
...
}
7.4.2 分组功能

weblog 项目接口分为前台和 Admin 后台,所以,除了在 weblog-web 模块中配置 Knife4j 外,还需要在 web-module-admin 也配置一份,并使用 Knife4j 分组功能将各自的接口隔离开来。

添加依赖、配置类和前面类似,注意类名,和 @Bean 的名称不能和 weblog-web 中的一样,否则会冲突。然后,改写分组名称,以及包扫描路径,还有 API 相关信息。

package com.bijing.weblog.admin.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;/*** @author 毕晶* @date 2024/2/20 13:42*/
@Configuration
@EnableSwagger2WebMvc
@Profile("dev")
public class Knife4jAdminConfig {@Bean("adminApi")public Docket createApiDoc() {Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInfo())// 分组名称.groupName("Admin 后台接口").select()// 这里指定 Controller 扫描包路径.apis(RequestHandlerSelectors.basePackage("com.bijing.weblog.admin.controller")).paths(PathSelectors.any()).build();return docket;}/*** 构建 API 信息** @return*/private ApiInfo buildApiInfo() {return new ApiInfoBuilder().title("Weblog 博客前台接口文档") // 标题.description("Weblog 是一款由 Spring Boot + Vue 3.2 + Vite 4.3 开发的前后端分离博客。") // 描述.termsOfServiceUrl("https://www.bilog.com/") // API 服务条款.contact(new Contact("bijing", "https://www.blog.com", "1457808125@qq.com")) // 联系人.version("1.0") // 版本号.build();}
}

在这里插入图片描述

7.5 拾遗

常用的Swagger相关注解以及它们的作用:

注解作用
@Api描述整个API的信息,包括标题、描述等。
@ApiOperation描述单个接口的信息,包括接口的标题、描述、请求方法等。
@ApiParam描述接口的参数信息,包括参数名、描述、是否必需等。
@ApiModel描述请求或响应的模型信息,包括模型的名称、描述等。
@ApiModelProperty描述模型的属性信息,包括属性的名称、描述、是否必需等。
@ApiIgnore用于忽略某个接口或模型,不会被Swagger文档化。
@ApiImplicitParam描述接口的隐式参数信息,一般用于描述请求头信息。
@ApiImplicitParams描述接口的多个隐式参数信息。
@ApiResponses描述接口的响应信息,包括不同响应状态码对应的描述。
@ApiModelProperly描述模型的属性信息。

8、自定义 Jackson 序列化、反序列化,支持 Java 8 日期新特性

8.1 自定义 Jackson 配置类

由于 Spring Boot 内置使用的就是 Jackson JSON 框架,所以,无需引入新的依赖,仅需添加自定义配置类即可,让其支持新的日期 API。

package com.bijing.weblog.common.config;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;/*** @author 毕晶* @date 2024/2/20 13:58*/
@Configuration
public class JacksonConfig {@Beanpublic ObjectMapper objectMapper() {// 初始化一个 ObjectMapper 对象,用于自定义 Jackson 的行为ObjectMapper objectMapper = new ObjectMapper();// 忽略未知字段(前端有传入某个字段,但是后端未定义接受该字段值,则一律忽略掉)objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// JavaTimeModule 用于指定序列化和反序列化规则JavaTimeModule javaTimeModule = new JavaTimeModule();// 支持 LocalDateTime、LocalDate、LocalTime的序列化和反序列化//定义LocalDateTime的序列化器javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));//定义LocalDateTime的反序列化器javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy" +"-MM-dd")));javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm" +":ss")));objectMapper.registerModule(javaTimeModule);// 设置时区objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));// 设置凡是为 null 的字段,返参中均不返回,请根据项目组约定是否开启// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);return objectMapper;}
}

完成自定义 Jackson 配置类后可以在原先的weblog-web模块下application.yml中删去jackson的一些无用配置。
在这里插入图片描述

8.2 拾遗

在Java 8中,LocalDate、LocalTime 和 LocalDateTime 是处理日期和时间的重要类。

  1. LocalDate:
  • LocalDate用于表示日期,不包含时间部分。
  • 它只包含年、月、日三个字段。
  • 例如,它可以用来表示员工的入职日期、某个活动的日期等。
  • LocalDate对象通常用于需要日期信息但不涉及时间的场景。
  1. LocalTime:
  • LocalTime用于表示时间,不包含日期部分。
  • 它只包含时、分、秒三个字段,可以包含纳秒精度。
  • 例如,它可以用来表示公交车的首发时间、会议的开始时间等。
  • LocalTime对象适用于那些只需要时间信息,而日期信息不重要的场景。
  1. LocalDateTime:
  • LocalDateTime是LocalDate和LocalTime的组合,它同时包含日期和时间。
  • 它包含年、月、日、时、分、秒六个字段,也可以包含纳秒精度。
  • LocalDateTime对象适用于需要同时记录日期和时间的场景,如在电商系统中记录交易发生的时间。

常用方法:

方法描述
LocalDate.now()获取当前日期
LocalDate.of(int year, int month, int dayOfMonth)创建特定日期
LocalTime.now()获取当前时间
LocalTime.of(int hour, int minute)LocalTime.of(int hour, int minute, int second)创建特定时间
LocalDateTime.now()获取当前日期时间
LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute)LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute, int second)创建特定日期时间
getYear()getMonthValue()getDayOfMonth()获取年、月、日
getHour()getMinute()getSecond()获取时、分、秒
plusDays(long daysToAdd)minusDays(long daysToSubtract)添加/减去指定天数
plusHours(long hoursToAdd)minusHours(long hoursToSubtract)添加/减去指定小时数
isEqual(LocalDate/LocalTime/LocalDateTime other)比较日期/时间/日期时间是否相等
isBefore(LocalDate/LocalTime/LocalDateTime other)isAfter(LocalDate/LocalTime/LocalDateTime other)比较日期/时间/日期时间的大小关系

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692836.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在同一个module里面集成多个数据库的多张表数据

确保本公司数据安全&#xff0c;通常对数据的管理采取很多措施进行隔离访问。 但是&#xff0c;Mendix应怎样访问散布于异地的多个数据库呢&#xff1f; 前几期我们介绍过出海跨境的大企业对于Mendix的技术、人才的诉求后&#xff0c;陆陆续续有其他客户希望更聚焦具体的实际场…

量子计算:数据安全难题

当今数字技术面临的最大挑战之一是安全系统和数据。为此&#xff0c;人们设计了复杂的算法来加密数据并通过称为对称加密的框架来保护数据。虽然这已被证明是成功的&#xff0c;但量子计算的进步&#xff08;利用量子力学比传统计算机更快地解决复杂问题&#xff09;可能会彻底…

【Pytorch 基础教程2】10分钟掌握Tensor基础 VSCode +Pytorch配置

Pytorch 基础教程 02 Tensor PyTorch 作为Numpy的代替品&#xff0c;可以使用GPU的强大计算能力 提供最大的灵活性和告诉的深度学习研究平台 这里补充上实验环境调试&#xff1a;第一次使用VS Code可以参考&#xff1a;PyTorch&#xff08;超详细&#xff09;部署与激活 举起Py…

优先队列C

由于看到P1629 邮递员送信这题,就去学了优先队列.为学习Dijkstra算法做准备 什么是优先队列 优先队列:是一种特殊类型的队列&#xff0c;每个元素都有一个相关的优先级。在优先队列中&#xff0c;元素按照优先级的顺序进行排列&#xff0c;具有最高&#xff08;或最低&#x…

Prometheus 教程

目录 一、简介二、下载安装1、安装 prometheus2、安装 alertmanager3、安装 grafana4、安装 node_exporter5、安装 mysqld_exporter 一、简介 Prometheus 是一个开源的系统监控和警报工具。它最初由 SoundCloud 开发&#xff0c;并于 2012 年发布为开源项目。Prometheus 专注于…

利用vite快速搭建vue3项目

1、选择一个文件夹&#xff0c;在vscode终端打开&#xff0c;输入命令【npm create vitelatest】 npm create vitelatest 2、提示你输入项目名称之后&#xff0c;我这里设置的是【rookiedemo】 3、回车之后&#xff0c;出现选择框架的提示&#xff0c;我们选择【vue】回车 4、…

js中使用for in注意事项,key的类型为string类型

for in是一个非常实用的存在&#xff0c;既可以遍历数组&#xff0c;又可以遍历对象&#xff0c;所以我一般都是会用来遍历可迭代的数据&#xff0c;遍历数组和对象的时候&#xff0c;要注意使用万能遍历方式&#xff1a; const users [1, 3, 45, 6]// const users {// 1…

Polyspace静态检测步骤

Polyspace 是一个代码静态分析和验证的工具&#xff0c;隶属于MATLAB&#xff0c;用于检测代码中的错误和缺陷&#xff0c;包括内存泄漏、数组越界、空指针引用等。帮助开发团队提高代码质量&#xff0c;减少软件开发过程中的错误和风险。 1、打开MATLAB R2018b 2、找到Polys…

AR智能眼镜主板硬件设计_AR眼镜光学方案

AR眼镜凭借其通过导航、游戏、聊天、翻译、音乐、电影和拍照等交互方式&#xff0c;将现实与虚拟进行无缝融合的特点&#xff0c;实现了更加沉浸式的体验。然而&#xff0c;要让AR眼镜真正成为便捷实用的智能设备&#xff0c;需要解决一系列技术难题&#xff0c;如显示、散热、…

Stable Diffusion 绘画入门教程(webui)-图生图

通过之前的文章相信大家对文生图已经不陌生了&#xff0c;那么图生图是干啥的呢&#xff1f; 简单理解就是根据我们给出的图片做为参考进行生成图片。 一、能干啥 这里举两个例子 1、二次元头像 真人转二次元&#xff0c;或者二次元转真人都行&#xff0c; 下图为真人转二次…

小清新卡通人物404错误页面源码

小清新卡通人物404错误页面源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云&#xff1a;https://wfr.lanzout.com/i6XbU1olftde

二叉树(6)——二叉树的创建和销毁

1 二叉树的创建 整体思路 将数组里的元素一直分为根&#xff0c;左子树&#xff0c;右子树&#xff0c;遇到#就返回NULL&#xff0c;链接到上层递归的左子树或者右子树&#xff0c;一定要把一个节点的左子树全部递归完才能返回到右子树。这种方法也可以scanf一个数组里的元素&…

Spring Boot项目怎么对System.setProperty(key, value)设置的属性进行读取加解密

一、前言 之前我写过一篇文章使用SM4国密加密算法对Spring Boot项目数据库连接信息以及yaml文件配置属性进行加密配置&#xff08;读取时自动解密&#xff09;&#xff0c;对Spring Boot项目的属性读取时进行加解密&#xff0c;但是没有说明对System.setProperty(key, value)设…

Docker Compose映射卷的作用是什么,dockerfile这个文件有什么区别和联系?

Docker Compose中映射卷&#xff08;Volumes&#xff09;的作用和Dockerfile之间既有区别也有联系。下面详细解释两者的作用、区别和联系&#xff1a; Docker Compose映射卷的作用 在Docker Compose中&#xff0c;卷&#xff08;Volumes&#xff09;用于数据持久化和数据共享…

【实战 JS逆向+python模拟获取+Redis token会话更新】实战模拟测试 某巴批发网 仅供学习

逆向日期&#xff1a;2024.02.20 使用工具&#xff1a;Node.js、python、Redis 加密方法&#xff1a;md5标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标…

day07-实战-今日指数

今日指数-day07 1.股票Code联想推荐 1.1 股票Code联想推荐功能介绍 1) 原型效果 输入框输入股票编码后&#xff0c;显示关联的股票信息; 2&#xff09;接口定义说明 接口说明&#xff1a; 功能描述&#xff1a;根据输入的个股代码&#xff0c;进行模糊查询&#xff0c;返…

Python学习-流程图、分支与循环(branch and loop)

十、流程图 1、流程图&#xff08;Flowchart&#xff09; 流程图是一种用于表示算法或代码流程的框图组合&#xff0c;它以不同类型的框框代表不同种类的程序步骤&#xff0c;每两个步骤之间以箭头连接起来。 好处&#xff1a; 1&#xff09;代码的指导文档 2&#xff09;有助…

云服务器ECS价格表出炉——阿里云

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

docker (十一)-进阶篇-docker-compos最佳实践部署zabbix

一 部署docker环境 关闭防火墙、selinux、开启docker&#xff0c;并设置开机自启动 注意点&#xff1a;docker部署的时候&#xff0c;bip要指定&#xff0c;不然会导致虚拟机ip和容器ip冲突&#xff0c;ssh连不上虚拟机 部署请参考 docker &#xff08;二&#xff09;-yum…

【python】windowslinux系统python的安装

一、python官网及下载路径 官网地址&#xff1a;Welcome to Python.org 下载路径&#xff1a;Download Python | Python.org ​​​​​​​ linux源码安装包下载&#xff1a; windows二进制安装包下载&#xff1a; 二、Linux如何安装python 2.1 单版本安装 以安装python…