瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现

embedded/2024/12/22 21:47:22/

瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现

文章目录

  • 3 项目组件优化
    • 3.1 实现Swagger文档输出
    • 3.2 实现logback日志打印
    • 3.3 实现表单校验功能
    • 3.4 实现请求参数和响应参数的打印

3 项目组件优化

Swagger_6">3.1 实现Swagger文档输出

  • 1)在application.yml中增加knife4j配置
spring:mvc:pathmatch:matching-strategy: ANT_PATH_MATCHER
knife4j:enable: truetitle: 瑞吉外卖group: ruiji_takeoutdescription: 瑞吉外卖version: 1.0name: itweidurl:email:base-package: com.itweid.takeout.controller
  • 2)创建配置类SwaggerProperties类接收配置
@Data
@ConfigurationProperties(prefix = "knife4j")
public class SwaggerProperties {private String title = ""; //标题private String group = ""; //组名private String description = ""; //描述private String version = ""; //版本private String name = ""; // 联系人private String url = ""; // 联系人urlprivate String email = ""; // 联系人emailprivate String basePackage = ""; //swagger会解析的包路径private List<String> basePath = new ArrayList<>(); //swagger会解析的url规则private List<String> excludePath = new ArrayList<>(); //在basePath基础上需要排除的url// 如果没有填写组名,则直接用标题作为组名public String getGroup() {if (group == null || group.isEmpty()) {return title;}return group;}
}
  • 3)创建自动配置类SwaggerAutoConfiguration进行初始化
@Configuration
@ConditionalOnProperty(name = "knife4j.enable", havingValue = "true", matchIfMissing = true)
@EnableSwagger2
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerAutoConfiguration implements BeanFactoryAware {@Autowiredprivate SwaggerProperties swaggerProperties;@Autowiredprivate BeanFactory beanFactory;@Bean@ConditionalOnMissingBeanpublic List<Docket> createRestApi(){ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;List<Docket> docketList = new LinkedList<>();ApiInfo apiInfo = new ApiInfoBuilder()// 页面标题.title(swaggerProperties.getTitle())// 创建人.contact(new Contact(swaggerProperties.getName(),swaggerProperties.getUrl(),swaggerProperties.getEmail()))// 版本号.version(swaggerProperties.getVersion())// 描述.description(swaggerProperties.getDescription()).build();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).groupName(swaggerProperties.getGroup()).select()// 为当前包路径.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage())).paths(PathSelectors.any()).build();configurableBeanFactory.registerSingleton(swaggerProperties.getGroup(), docket);docketList.add(docket);return docketList;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}
}
  • 4)重新启动项目,在浏览器访问http://localhost:8081/doc.html,即可查看Swagger文档:


Swagger文档的调试功能中,可以直接进行测试:

logback_111">3.2 实现logback日志打印

  • 1)引入依赖
<!--logback-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.3</version>
</dependency>
  • 2)在resources目录下创建配置文件

  • logback-base.xml

<?xml version="1.0" encoding="UTF-8"?>
<included><contextName>logback</contextName><!--name的值是变量的名称,value的值时变量定义的值定义变量后,可以使“${}”来使用变量--><property name="log.path" value="logs" /><!-- 彩色日志 --><!-- 彩色日志依赖的渲染类 --><conversionRuleconversionWord="clr"converterClass="org.springframework.boot.logging.logback.ColorConverter" /><conversionRuleconversionWord="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}}"/><!--输出到控制台--><appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/Business.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender>
</included>
<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--引入其他配置文件--><include resource="logback-base.xml" /><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。addtivity:是否向上级logger传递打印信息。默认是true。--><!--开发环境--><springProfile name="dev"><logger name="com.itweid.takeout" additivity="false" level="debug"><appender-ref ref="LOG_CONSOLE"/></logger></springProfile><!--生产环境--><springProfile name="pro"><logger name="com.itweid.takeout" additivity="false" level="info"><appender-ref ref="LOG_FILE"/></logger></springProfile><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 默认是DEBUG可以包含零个或多个元素,标识这个appender将会添加到这个logger。--><root level="info"><appender-ref ref="LOG_CONSOLE" /><appender-ref ref="LOG_FILE" /></root>
</configuration>
  • 3)重新启动项目,可以看到根目录下生成了logs文件夹及日志文件:

3.3 实现表单校验功能

员工登录时,必须输入用户名和密码,虽然前端JS进行了校验,但对于后端来说,前端传来的数据是不可信的。

前端很容易获取到后端的接口,如果有人直接调用接口,就可能会出现非法数据,因此服务端也要数据校验。总的来说:

  • 前端校验:主要是提高用户体验
  • 后端校验:主要是保证数据安全可靠

Hibernate Validator框架可以以很优雅的方式实现参数的校验,让业务代码和校验逻辑分开,不再编写重复的校验逻辑。

更详细的用法可参考:后台管理系统的通用权限解决方案(五)SpringBoot整合hibernate-validator实现表单校验

  • 1)首先,在LoginForm类中加入表单校验的注解,如字符串类型的参数则用@NotBlank
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("登录表单")
public class LoginForm {@ApiModelProperty("用户名")@NotBlank(message = "用户名不能为空")private String username;@ApiModelProperty("密码")@NotBlank(message = "密码不能为空")private String password;
}
  • 2)在EmployeeController类中使用@Validated注解开启校验功能:

  • 3)需要特别注意的是,在2.3.0版本之前,spring-boot-starter-web是集成了validation检验的,但是在2.3.0开始就去掉了该依赖,所以根据实际版本决定是否添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • 4)重启服务,发起登录请求,如果用户名为空,则会报错:


但此时前端提示不太友好(报400)。我们还要继续完善一下,对异常进行统一处理。

  • 5)自定义一个CustomException异常类来统一处理已知的异常。未来在业务逻辑中,使用try...catch...捕获异常后,再抛出一个CustomException异常:
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class CustomException extends RuntimeException {private BaseResult result;/*** 指定一个是否追踪信息栈的异常*/public CustomException(BaseResult result, boolean writableStackTrace) {super(result.getMsg(), null, false, writableStackTrace);this.result = result;}/*** 指定一个不追踪信息栈的异常*/public CustomException(BaseResult result) {super(result.getMsg(), null, false, false);this.result = result;}/*** 指定一个不追踪栈信息的异常*/public CustomException(ErrorCode errorCode) {super(errorCode.getMsg(), null, false, false);this.result = BaseResult.error(errorCode);}
}
  • 6)创建一个全局异常处理类GlobalExceptionHandler,对自定义异常和参数绑定异常进行统一处理:
@ControllerAdvice(annotations = { RestController.class, Controller.class })
@Slf4j
public class GlobalExceptionHandler {/*** 自定义异常的处理*/@ExceptionHandler(CustomException.class)@ResponseBodypublic BaseResult customExceptionHandler(CustomException customException) {log.error("捕获自定义异常:{}", customException.getResult().getMsg(), customException);return customException.getResult();}/*** 参数绑定异常的处理*/@ExceptionHandler({ConstraintViolationException.class, BindException.class})@ResponseBodypublic String validateException(Exception e, HttpServletRequest request) {log.error("捕获参数异常:{}", e.getMessage(), e);String msg = null;if (e instanceof ConstraintViolationException) {ConstraintViolationException constraintViolationException =(ConstraintViolationException) e;Set<ConstraintViolation<?>> violations =constraintViolationException.getConstraintViolations();ConstraintViolation<?> next = violations.iterator().next();msg = next.getMessage();} else if (e instanceof BindException) {BindException bindException = (BindException) e;msg = bindException.getBindingResult().getFieldError().getDefaultMessage();}log.error("参数异常信息:{}", msg);return msg;}}
  • 7)重启服务,再次调用登录请求,当参数不符合要求时,则会返回更加友好的提示:

3.4 实现请求参数和响应参数的打印

页面的每个请求都有请求参数和响应参数,如果每个请求都单独打印这些参数,则显得非常冗余。

为此我们可以基于注解和切面编程,实现请求参数和响应参数的打印。

  • 1)引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--hutool工具-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version>
</dependency>
  • 2)创建切面类OptLogAspect,配置切入点拦截规则,拦截所有Controller方法
@Aspect
@Slf4j
public class OptLogAspect {/*** 定义Controller切入点拦截规则,拦截 @OptLog 注解的方法*/@Pointcut("execution(public * com.itweid.takeout.controller.*Controller.*(..))")public void optLogAspect() {}
}
  • 3)在OptLogAspect的前置通知方法中,打印请求参数信息
/*** 前置通知*/
@Before(value = "optLogAspect()")
public void doBefore(JoinPoint joinPoint) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();// 请求参数Object[] args = joinPoint.getArgs();String strArgs = "";try {if (!request.getContentType().contains("multipart/form-data")) {strArgs = JSONUtil.toJsonStr(args);}} catch (Exception e) {try {strArgs = Arrays.toString(args);} catch (Exception ex) {log.warn("解析参数异常", ex);}}log.info("请求参数:{}", StrUtil.sub(strArgs, 0, 65535));
}
  • 4)在成功返回通知方法和异常返回通知中,打印响应参数信息
/*** 成功返回通知*/
@AfterReturning(returning = "ret", pointcut = "optLogAspect()")
public void doAfterReturning(Object ret) {BaseResult baseResult = Convert.convert(BaseResult.class, ret);log.info("响应参数:{}", baseResult);
}/*** 异常返回通知*/
@AfterThrowing(throwing = "e", pointcut = "optLogAspect()")
public void doAfterThrowable(Throwable e) {log.info("响应异常:{}", getStackTrace(e));
}public static String getStackTrace(Throwable throwable) {StringWriter sw = new StringWriter();try (PrintWriter pw = new PrintWriter(sw)) {throwable.printStackTrace(pw);return sw.toString();}
}
  • 5)在WebMvcConfig配置类中注册切面类为Bean
@Bean
public OptLogAspect optLogAspect() {return new OptLogAspect();
}
  • 6)重启服务,测试登录功能


可见,请求参数和响应参数成功打印。后续还可以将请求IP、操作员等信息收集起来存到数据库,就可以实现常说的审计功能。

本节完,更多内容查阅:瑞吉外卖项目实战


http://www.ppmy.cn/embedded/147916.html

相关文章

Flink调优----反压处理

目录 概述 1.1 反压的理解 1.2 反压的危害 定位反压节点 2.1 利用 Flink Web UI 定位 通过 WebUI 看到 Map 算子处于反压&#xff1a;​编辑 分析瓶颈算子 2.2 利用 Metrics 定位 根据指标分析反压 可以进一步分析数据传输 反压的原因及处理 3.1 查看是否数据倾斜 …

Android settings命令详解

文章目录 Android 中的 settings 命令详细介绍基本语法使用示例1. 查看设置值2. 修改设置值3. 删除设置项 命令选项1. get 子命令2. put 子命令3. delete 子命令 命名空间详解1. system2. secure3. global 常见设置项全局设置&#xff08;global&#xff09;安全设置&#xff0…

热更新解决方案4——xLua热补丁

概述 运行时不在执行C#中的代码&#xff0c;而是执行Lua中的代码&#xff0c;相当于是打了个补丁。 1.第一个热补丁 2.多函数替换 3.协程函数替换 在原HotfixMain脚本中只加个协程函数即可&#xff08;和在Start中启动协程函数&#xff09; 4.索引器和属性替换 在HotfixMain中…

常用的消息中间件(ActiveMQ、RabbitMQ、RocketMQ、Kafka)面试精华

目录 主要作用&#xff1a; 四种消息中间件&#xff1a; 主要作用&#xff1a; 解耦 多个系统调用主系统可选择是否订阅 异步 主系统可提高响应时间 削峰 通过消息队列减轻消息访问对服务器的压力 四种消息中间件&#xff1a; ActiveMQ 万级吞吐量 RabbitMQ(中小型…

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法&#xff08;英语&#xff1a;Dijkstras algorithm&#xff09;&#xff0c;又称迪杰斯特拉算法、Dijkstra算法&#xff0c;是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程&#xff1a; 1.首先设置开始节点的成本值为0&#xff0c;并将…

使用Python实现天文数据分析:探索宇宙的奥秘

天文学是一门通过观测和分析天体来研究宇宙结构和演化规律的科学。随着观测技术的进步&#xff0c;天文学家们积累了大量的天文数据。通过对这些数据的分析&#xff0c;我们可以揭示宇宙中的诸多奥秘。Python作为一种功能强大且易用的编程语言&#xff0c;为天文数据分析提供了…

指令v-on 调用传参

在Vue.js中&#xff0c;可以使用指令v-on来给元素绑定事件。v-on指令可以接收一个事件名称作为参数&#xff0c;还可以传递额外的参数给事件处理函数。以下是对v-on指令调用传参的详细解析与代码实例。 当使用v-on指令调用事件处理函数时&#xff0c;可以使用冒号&#xff08;…

PDF无法打印!怎么办?

打开PDF文件之后&#xff0c;发现文件不能打印&#xff1f;这是什么原因&#xff1f;首先我们需要先查看一下自己的打印机是否能够正常运行&#xff0c;如果打印机是正常的&#xff0c;我们再查看一下&#xff0c;文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…