基础篇-00_SpringBoot3_Vue3导学课程_哔哩哔哩_bilibili 这个是视频链接
这个新课程里面用了一些企业里会用的注解例如Validated这种,业务流程清晰明了简单上手,算是可以了解最基本的Springboot开发流程,方便上手和快速入门
主要是下面这几个部分
目录
自定义校验数据类StateValidation实现ConstraintValidator接口
引入Validation依赖
引入springboot的Validation起步依赖,这个依赖的作用是对应参数使用的注解生效
这个依赖提供了许多的注解例如
-
Email
-
Pattern
-
NotNull
-
NotEmpty
-
URL
-
等等
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>3.2.5</version> </dependency>
我们看看Controller里面用的Pattern注解,这个就是Validation提供的注解
我们之前要写一大堆的逻辑,来判断username和password是否符合规范
但是我们可以使用Pattern注解直接写正则表达式,这样子就可以简化很多流程了
我们来看看这个项目中Validation其他注解
Validated注解,我们用在Controller上,这个是让我们的实体类User里面的注解生效
看看我们的实体类User
NotNull 不能不传
NotEmpty 必须是字符串,且不能是空字符串
记住NotNull和NotEmpty是有区别的
Pattern 使用正则表达式
Email 判断是否是合理的邮箱
URL 判断是否是URL
这些注解就是Validation提供的注解
但是我们的Validation注解要结合全局异常处理器来使用,不然抛出异常的话我们无法处理
全局异常处理器
例如我们方法上面的注解Pattern匹配失败了,那么它就会报异常
而且它是整个方法都异常了,所以我们不能try catch finally
所以我们要写一个全局异常处理器,定义通用异常抛
创建全局异常处理器
我们自己创建一个全局异常处理器类,GlobalExceptionHandler
注意我们有两个注解
RestControllerAdvice
ExceptionHandler(Exception.class)
RestControllerAdvice,因为我们抛出异常的地方在我们的RestController中
ExceptionHandler(Exception.class)这个注解要写到我们要抛出异常的类上
指定要处理的类的类型是Result类
你看我们Controller里面也就是写了那个Pattern注解的,就是可能抛出异常的,类型是Result类型
所以我们的ExceptionHandler(Exception.class)对应的方法要是Result类
e.getMessage是获取我们的错误信息,但是有写异常我们获取不到错误信息,这样子返回给前端不太友好
所以
StringUtils.hasLength我们用这个来判断我们是否e.getmessage是否有信息
记得我们的Validation注解要结合 全局异常处理器来使
拦截器HandlerInterceptor
自定义拦截器
jwt我们就不说了,因为我们不可能在每一个Controller都解析我们的token判断是否登录,所以我们在拦截器里面来解析我们的token,如果我们token解析成功的话,我们就放行
我们的拦截器记得加Component注解来扫描包,然后注入到我们IOC容器
我们自己弄一个类名字叫LoginInterceptor,然后连接HnadlerInterceptor接口,来重写里面的方法
例如我们的preHandler,也就是我们拦截前
我们从请求头里面拿出我们的token,然后解析,如果不报错解析成功的话,我们就return true放行
否则我们就拦截return false
在配置类里面注册拦截器
然后我们把自己写的拦截注册我们的WebConfig配置类里面,也就是我们的WebMvcConfigurer
里面有一个addInterceptors方法,就是添加我们的拦截器
配置类上面要记得加上Configuration注解,使我们的配置生效
我们因为之前把我们自定义拦截器Intercptor弄到IOC容器里面了,所以我们这里可以引入然后AutoWired自动注入
然后我们在addInterceptors()里添加我们的拦截器,但是我们有两个路径是不能拦截的
也就是我们的登录接口和注册接口,所以我们用**excludePathPatterns()**里面不拦截我们的登录接口和注册接口
然后修改我们的Controller,因为的解析Token的流程已经弄到拦截器里面了,我们不需要在Controller里面来解析Token
ThreadLocal的使用
如果我们想要使用我们存到Token里面的信息的时候,我们就要从请求头获取然后解析
但是我们不可能每次想要用这些信息,然后就每次都从请求头拿出来我们的Token解析吧,这样子太麻烦了,我们直接优化成TreadLocal
ThreadLocal线程池
我们的线程池有线程隔离的效果,我们一般往里面存类似于用户名这种东西,让这个能全局使用
方便取出来弄sql语句或者调用其他函数之类的
我们可以用TreadLocal来存全局变量,这样子就方便了很多
我们要改成从ThreadLocal里面获取我们的数据,但首先我们要往我们的ThreadLocal里面存取我们的数据
一般来说我们要new 一个ThreadLocal线程池 然后用set get remove三个方法
黑马把这些步骤封装成了一个工具类,我们直接使用就行
preHandle
我们在拦截器的逻辑里面,把我们的业务数据存储到我们的ThreadLocal中
因为我们token解析的时候是解析成Map的
我们在拦截器里面,把解析token得到的Map存到我们的ThreadLocal线程池里面
因为我们之前是存了一个map进我们的ThreadLocal
所以我们拿出来的时候,也是一个Map
然后我们用map.get()来取出我们的key对应的
其实我们这个map里面一般放的就是我们的username,id这些,方便我们全局调用的变量
我们还要在线程结束之后把我们的往线程池里存的map移出去,防止占用内存拦截结束后,我们还要记得after之后,清空我们的TreadLocal里面的东西
afterCompletion
所以拦截器里面我们要多重写一个方法,afterCompletion
自定义注解来做参数校验
分为三步
一 自定义注解State
二 自定义校验数据类StateValidation实现ConstraintValidator接口
三 在需要校验的地方使用自定义注解
自定义注解State
我们的类是@interface,我们要写的是注解
@Documented
这是一个元注解(meta-annotation)用于标记该注解应该包含在生成的文档中
@Target( ElementType.FIELD)
元注解,作用在属性上
@Retention(RetentionPolicy.RUNTIME )
元注解,注解在我们运行阶段任然要保留,我们这里是Runtime所以是运行阶段要保留
@Constraint(validatedBy = {})
用来指定将来谁给我们自己写的state注解提供校验规则,并指定了 StateValidation.class 作为校验规则的提供者
提供校验失败后的提示信息
String message() default "{jakarta.validation.constraints.NotEmpty.message}";
指定分组
Class<?>[] groups() default {};
负载,获取到State注解的附加信息
Class<? extends Payload>[] payload() default {};
因为我们指定了 StateValidation类作为我们的校验规则,所以我们要写一个这个类出来,然后里面实现方法
自定义校验数据类StateValidation实现ConstraintValidator接口
创建规则类,实现 ConstraintValidator接口
我们的ConstraintValidator接口有两个参数
第一个参数,给哪个注解提供校验规则
第二个参数,校验的数据类型
我们是给我们刚刚的State注解提供校验规则,然后我们检验的数据类型为String
重写isValid方法,在isValid这个方法里写我们的校验规则
在需要校验的地方使用自定义注解
然后我们就可以把State注解写到我们的实体类里面
(小重点)分组校验的实现
分组校验
我们添加一下发现添加失败了,失败的原因是我们的id不能为null
因为我们刚刚实体类的ID设置了notnull
但是我们新增和删除,一个不需要传id,一个需要传id
那我们可不可以用校验来进行分组呢?
添加的时候的时候我们需要传ID
新增的时候我们不需要传ID
所以我们来弄一下分组校验
定义分组
我们用groups来指定我们的注解是哪一个校验组的
你看我们在这个运行失败的实体类下面弄多了两个自定义接口,名称就好和用途一样
public interface Add extends Default {};
public interface update extends Default{};
一个是Add代表添加,一个是Update代表更新
定义校验项指定归属的分组
然后我们在Controller里的Validated,指定我们的生效的类型
检验时指定要检验的分组
如果不指定的话,默认分为为Default,默认组
我们一开始时这样子写,但是我们只定义了Add和Update这两个分组
所以我们就算不些这两个,我们默认分组就包含这两个了
因为我们这样子继承默认分组
就说明我们这两个类其实默认都有了这两个指定对象
所以我们不用大费周章所有都写
(groups = Add.class,update.class),因为如果不写默认就是这两个
使用图床替代OOS上传图片简化实现
我们可以弄图床,然后后端只存我们的url,就可以拿到我们的图片了,我们就不用使用oos
我们随便找一个免费图床,上传图片然后把url存入到我们的数据库就行了,可以不给予OOS简化实现
Redis实现登录逻辑优化
因为jwt令牌无法在某些特定场景做到主动过期,所以我们要结合Redis来做token主动过期
因为我们之前是在jwt里面设置过期时间,拦截器里面解析token,如果解析成功就放行,解析失败就拦截。但是如果我们修改密码之后,我们会得到一个新的token,但是我们旧的token还没有过期,按照之前的逻辑,如果有人能拿到我们的旧的token写入前端请求头,它还是能通过我们的拦截器然后实现登录
所以我们要结合redis优化登录,当更新密码之后,我们往redis里面存新的token,然后主动过期旧的token
所以我们登录的时候,我们要把token存到redis里面
修改密码之后,我们要把原本的token从redis中删除
然后我们的拦截器的逻辑就是从我们的Redis里面来取出我们的token,然后再来用jwt解析token
多环境开发
配置优先级
我们的属性配置有这四种方式
项目中的application.yml文件
jar包目录下的application.yml文件
操作系统环境变量
命令行参数
然后我们的配置的优先级,从上往下递增,下面的优先级高
基本使用
分为单文件配置和多文件配置
单文件配置
Profile隔离应用程序配置
首先我们用spring:config来弄三个不同的环境,分别是test,dev和pro
spring:config:activates:on-profile
spring:profiles:active,来指定哪一个环境生效
然后我们在spring:profiles:active的下面配置我们的通用属性
我们的自定义配置属性会覆盖我们的通用属性
多文件配置
我们也可以不在一个配置文件来写,我们可以直接写多个配置文件
例如这样子
我们要在通用配置里面来指定激活的环境,我们的application.yml就是我们的通用配置,其他就是其他配置,我们可以在通用配置里面来指定使用其他配置
多环境分组
如果我们直接这样子写的话,我们的application文件太长了,不利于维护
所以我们使用分组,不同的配置文件写不同的内容,然后弄成同一组统一生效
例如我们的server就写端口配置
我们的DB就写数据库配置
我们的self就写自定义配置
然后我们把这三个配置文件弄成一组
active来指定我们哪个配置文件生效,这样子就是我们整个组都生效了,简洁还方便维护
JWT的使用
引入依赖
首先我们要引入一个java-jwt依赖
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
然后我们使用jwt就行了
jwt令牌的创建
载荷是用来存我们的信息的,我们要把信息存到map里面
然后添加载荷,用"user"当变量存进去,以后取出来也用"user"取
然后withExpiresAt添加过期时间
用sign()里面指定我们的加密算法,
Algorithm是我们的jwt提供的一个加密算法
Algorithm.HMAC256("KIRA")然后我们用“KIRA”来当作这个算法的密钥
jwt令牌的解析
JWTVerifier jwtVerifier =
JWT.require(Algorithm.HMAC256("KIRA"))
.build();
我们的解析算法和密钥要和我们加密的时候一模一样我们才能成功解析出来
用构造来弄我们的token解析器,类型是JWTVerifier
使用方法vertify(token),来解析我们的token
从解析出来的token拿出Map,这个Map里面存了我们之前存进去的一些数据
dcoded JWT.getClaims()
这个解析出来的存数据的Map的类型是Map<String,Claim>
之前我们的数据是存到"user"这里的,所以我们取出存的数据的时候用"user"取出
然后我们把这个生成token和解析token弄到一个工具类里面,这样子就简化了代码方便了使用。
驼峰映射
在application文件中开启驼峰映射,这样子我们的sql语句里面的字段才可以自动转换匹配mysql数据库的字段
动态SQL语句
因为我们有些参数不是必须传的,所以我们的后端的SQL语句不能写死,所以我们要写一个xml文件来写一个动态SQL语句,来实现动态传参数
记住,我们的Resource的Mapper的目录,要和上面我们写的Mapper的目录一一对应
看看我们目录的对应
Resource其实就是我们的根目录
然后我们安装一个Mybatis插件,如果我们的xml文件和Mapper文件对应上的了话,左边就会有Mapper的标志
其他常用注解
JsonIgnore
这个注解的用处是,当我们这个类转化成Json传输的时候,这个参数不参与转换成Json,这样子我们的密码这种隐私信息就不会返回给前端了
Data
lombok的一个自动赋值注解,get,set
Validated
我们直接在这个Controller类上面用Validated注解,这样子下面使用的Validation注解全都可以生效了
也可以让POJO实体类里的Validation注解生效
JsonFormat
用正则表达式来格式化我们的日期
RequestHeader 请求头
RequestParam 参数
RequestBody 请求体
上面三个不必多说
PathVariable 要求路径参数名和形参名一一对应
例如这种
Override
小提一嘴,这个注解的意思是这个方法是父类的方法,我们可以重写