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

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

文章目录

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

3 项目组件优化

3.1 实现Swagger文档输出

  • 1)在application.yml中增加knife4j配置
spring:
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER
knife4j:
  enable: true
  title: 瑞吉外卖
  group: ruiji_takeout
  description: 瑞吉外卖
  version: 1.0
  name: itweid
  url:
  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 = ""; // 联系人url
    private String email = ""; // 联系人email
    private 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 {

    @Autowired
    private SwaggerProperties swaggerProperties;
    @Autowired
    private BeanFactory beanFactory;

    @Bean
    @ConditionalOnMissingBean
    public 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;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
  • 4)重新启动项目,在浏览器访问http://localhost:8081/doc.html,即可查看Swagger文档:


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

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" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule
            conversionWord="clr"
            converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule
            conversionWord="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>
  • logback-spring.xml
<?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)
    @ResponseBody
    public BaseResult customExceptionHandler(CustomException customException) {
        log.error("捕获自定义异常:{}", customException.getResult().getMsg(), customException);
        return customException.getResult();
    }

    /**
    * 参数绑定异常的处理
    */
    @ExceptionHandler({ConstraintViolationException.class, BindException.class})
    @ResponseBody
    public 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、操作员等信息收集起来存到数据库,就可以实现常说的审计功能。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenEuler 22.03 安装 flink-1.17.2 集群

零&#xff1a;规划 本次计划安装三台OpenEuler 22.03 版本操作系统的服务器&#xff0c;用于搭建 flink 集群。这里使用flink1.17.2 的原因&#xff0c;是便于后续与springboot的整合 服务器名IP地址作用其他应用flink01192.168.159.133主jdk11、flink-1.17.2flink02192.168.…

[数据结构] 链表

目录 1.链表的基本概念 2.链表的实现 -- 节点的构造和链接 节点如何构造? 如何将链表关联起来? 3.链表的方法(功能) 1).display() -- 链表的遍历 2).size() -- 求链表的长度 3).addFirst(int val) -- 头插法 4).addLast(int val) -- 尾插法 5).addIndex -- 在任意位置…

20241220在荣品开发板PRO-RK3566的buildroot下适配gc2093

20241220在荣品开发板PRO-RK3566的buildroot下适配gc2093 2024/12/20 16:00 余顺?PRO-RK3566开发板 挂 gc2093模块。刷 buildroot的预编译固件。 update-pro-rk3566-buildroot-hdmi-20231130-034633.img 1、现在发现 qcamera的 拍照Capture、Record录像模式都是640x480分辨率…

实习冲刺数据库练习-01 基础查询

原题链接&#xff1a;牛客网在线编程_SQL篇_非技术快速入门 数据表示例&#xff1a; 根据数据表示例要求我们完成以下查询&#xff1a; &#xff08;1&#xff09;获取用户信息表中所有的数据&#xff0c;请你取出相应结果 &#xff08;2&#xff09;获取用户的设备id对应的…

【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换

相关链接&#xff1a; http://mars3d.cn/editor-vue.html?keyex_1_2_1&idmap/other/backgroundImg 实现代码&#xff1a; export function show1() {map.setOptions({scene: {backgroundType: "image",backgroundImage: "url(//data.mars3d.cn/img/busin…

telnet命令检查端口

1、简介 telnet是一种用于远程登录的协议&#xff0c;可以通过telnet客户端连接到远程主机&#xff0c;并在远程主机上执行命令。 2、使用telnet命令检查端口 2.1 进入linux终端 2.2 输入telnet命令 如果没有安装telnet命令&#xff0c;请执行以下命令安装 sudo yum install…

Unity 根据文本宽度自动移动图像位置

游戏中有时候需要变动的显示一个物品的数量&#xff0c;变化的文本宽度不停的变化&#xff0c;这时候需要将物品的icon随着文本的长度而改变位置。 实现思路&#xff1a;使用Content Size Fitter来动态改变内容的大小。 首先建立一个文本组件&#xff0c;添加Content Size Fi…

基于Springboot人口老龄化社区服务与管理平台【附源码】

基于Springboot人口老龄化社区服务与管理平台 效果如下&#xff1a; 系统登陆页面 系统主页面 社区信息页面 社区文件页面 活动报名页面 走访任务管理页面 社区资讯页面 老人信息管理页面 研究背景 随着社会老龄化的加剧&#xff0c;老年人口比例逐渐增加&#xff0c;对老年…

加密数据库在现代企业中的应用实践

以下是对加密数据库在现代企业中的应用实践的详细阐述&#xff1a; 一、加密数据库的应用背景 随着信息技术的飞速发展&#xff0c;现代企业对于数据的安全性和隐私保护要求越来越高。数据库作为存储大量敏感信息的关键设施&#xff0c;其安全性直接关系到企业的商业利益和声誉…

安卓环境配置及打开新项目教程,2024年12月20日最新版

1.去官网下载最新的Android Studio&#xff0c;网址&#xff1a;https://developer.android.com/studio?hlzh-cn 2.下载加速器&#xff0c;注册账号&#xff0c;开启加速器。网址&#xff1a;放在文末。 3.下载安卓代码&#xff0c;项目的路径上不能有中文&#xff0c;特别是…

20241217使用M6000显卡在WIN10下跑whisper来识别中英文字幕

20241217使用M6000显卡在WIN10下跑whisper来识别中英文字幕 2024/12/17 17:21 缘起&#xff0c;最近需要识别法国电影《地下铁》的法语字幕&#xff0c;使用 字幕小工具V1.2【whisper套壳/GUI封装了】 无效。 那就是直接使用最原始的whisper来干了。 当你重装WIN10的时候&#…

sqlite3 支持位运算 和view和 triger

数据设置条件以后可以.根据门限自动调整其他的值 由数据库记录修改时间,及记录-> 网元设备的告警产生时间,设置超时清除时间,记录系统的原始时间戳 CPp 有 sqlite 支持 json 导出字符串,json 库将字符串,映射为结构体 triger update table 更新到一个 可设置参数列表 ,view …

11-C语言结构体(下篇)

一、结构体指针变量 结构体指针变量&#xff1a;本质上是一个指针变量&#xff0c;保存的是结构体变量的地址。 1.结构体变量的地址 结构体变量的地址&#xff1a;对结构体变量名取地址。 代码演示 typedef struct stu {char name[32];int age;float score; }STU;int main…

linux普通用户使用sudo不需要输密码

1.root用户如果没有密码&#xff0c;先给root用户设置密码 sudo passwd root #设置密码 2.修改visudo配置 su #切换到root用户下 sudo visudo #修改visudo配置文件 用户名 ALL(ALL) NOPASSWD: ALL #下图所示处新增一行配置 用户名需要输入自己当前主机的用户名

百度面试手撕 go context channel部分学习

题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…

任务一登录安全加固

1 &#xff08;1&#xff09;、&#xff08;2&#xff09; secpol.msc打开本地安全策略 2 &#xff08;1&#xff09; DCOM: 在安全描述符定义语言(SDDL)语法中的计算机访问限制 没有定义 DCOM: 在安全描述符定义语言(SDDL)语法中的计算机启动限制 没有定义 Microsoft 网络服…

无人机推流直播平台EasyDSS视频技术如何助力冬季森林防火

冬季天干物燥&#xff0c;大风天气频繁&#xff0c;是森林火灾的高发期。相比传统的人力巡查&#xff0c;无人机具有更高的灵敏度和准确性&#xff0c;尤其在夜间或浓雾天气中&#xff0c;依然能有效地监测潜在火源。 无人机可以提供高空视角和实时图像传输&#xff0c;帮助巡…

写SQL太麻烦?免费搭建 Text2SQL 应用,智能写 SQL | OceanBase AI 实践

自OceanBase 4.3.3版本推出以来&#xff0c;向量检索的能力受到了很多客户的关注&#xff0c;也纷纷表达希望OB能拓展更多 多模数据库大模型 的AI应用实践。 在上篇文章 &#x1f449; OceanBase LLM&#xff0c;免费构建你的专属 AI 助手 &#xff0c;我们介绍了如何去搭建一…

Halcon 机器视觉案例 之 药剂液面高度测量

第二篇 机器视觉案例 之 药剂液面高度测量 文章目录 第二篇 机器视觉案例 之 药剂液面高度测量1.案例要求2.实现思路2.1获得液面的位置&#xff1a;2.1.1 获得每支药剂的位置坐标2.1.2 根据药剂的横坐标设置卡尺工具助手找到每一个液面的位置 2.2 获得基准线的位置&#xff1a;…

使用k6进行MongoDB负载测试

1.安装环境 安装xk6-mongo扩展 ./xk6 build --with github.com/itsparser/xk6-mongo 2.安装MongoDB 参考Docker安装MongoDB服务-CSDN博客 连接成功后新建test数据库和sample集合 3.编写脚本 test_mongo.js import xk6_mongo from k6/x/mongo;const client xk6_mongo.new…