黑马大事件学习笔记

33 阅读7分钟

知识点

JWT-令牌工具

组成

  1. 标头(Header)
  2. 有效载荷(Payload)
  3. 签名(Signature)

token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz

使用

  1. 引入java-jwt坐标

    <dependency>
    	<groupId>com.auth0</groupId>
    	<artifactId>java-jwt</artifactId>
    	<version>4.4.0</version>
    </dependency>
    
  2. 导入JWT工具类

    // utils/JwtUtil.java
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    
    import java.util.Date;
    import java.util.Map;
    
    public class JwtUtil {
    
        private static final String KEY = "simon";
    
        //接收业务数据,生成token并返回
        public static String genToken(Map<String, Object> claims) {
            return JWT.create()
                    .withClaim("claims", claims)
                    .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                    .sign(Algorithm.HMAC256(KEY));
        }
    
        //接收token,验证token,并返回业务数据
        public static Map<String, Object> parseToken(String token) {
            return JWT.require(Algorithm.HMAC256(KEY))
                    .build()
                    .verify(token)
                    .getClaim("claims")
                    .asMap();
        }
    
    }
    
  3. 调用API生成令牌

    生成:

    Map<String, Object> claims = new HashMap<>();
    claims.put("id", user.getId());
    claims.put("username", user.getUsername());
    String token = JwtUtil.genToken(claims);
    return Result.success(token);
    
  4. 解析令牌,抛出异常,说明令牌被篡改或过期了

    // 令牌认证
    String token = request.getHeader("Authorization");
    // 验证token
    try {
        JwtUtil.parseToken(token);
        return true;
    } catch (Exception e) {
        response.setStatus(401);
        return false;
    }
    

常见异常信息

SignatureVerificationException 签名不一致异常 TokenExpiredException 令牌过期异常 AlgorithmMismatchException 算法不匹配异常 InvalidClaimException 失效的payload异常(传给客户端后,token被改动,验证不一致)

拦截器-身份校验

自定义拦截器

  1. 继承HandlerInterceptor接口
  2. 重写preHandle方法
// Interceptor/LoginInterceptor.java
@Component
public class LoginInterceptor implements HandlerInterceptor { 
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 令牌认证
        String token = request.getHeader("Authorization");
        // 验证token
        try {
            JwtUtil.parseToken(token);
            return true;
        } catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }
}

true: 放行

false: 不放行

preHandle: 请求到达接口前执行

afterCompletion: 请求(接口)结束后执行

在配置文件中注册拦截器

  1. 继承WebMvcConfigurer接口
  2. 注入拦截器
  3. 重写addInterceptors方法,注册拦截器
// config/WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register"); // 排除登录和注册接口
    }
}

ThreadLocal-提供线程局部变量

  • 用来存取数据:set()/get()
  • 使用ThreadLocal存储的数据,线程安全
  • 在同一个线程间共享数据

使用

  1. 导入工具类
/**
 * ThreadLocal 工具类
 */
@SuppressWarnings("all")
public class ThreadLocalUtil {
    //提供ThreadLocal对象,
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();

    //根据键获取值
    public static <T> T get(){
        return (T) THREAD_LOCAL.get();
    }
    
    //存储键值对
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }


    //清除ThreadLocal 防止内存泄漏
    public static void remove(){
        THREAD_LOCAL.remove();
    }
}
  1. 在拦截器中调用set方法存入数据

image-20240131203413727.png 3. 在需要获取数据的位置调用get方法

image-20240131204125028.png

  1. 释放内存

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清空ThreadLocal中的数据
        ThreadLocalUtil.remove();
    }
    

validation-参数校验

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

校验参数

在参数前添加@Pattern注解,设定regexp参数为正则表达式

image-20240201164149652.png

校验类的属性值

  1. 在实体类的属性前添加以下注解
注解作用
@Pattern必须符合给定的正则表达式
@Null被注解的元索必须为空
@NotNull值不能为null
@NotEmpty值不能为null,且内容不为空
@Email满足邮箱格式
@URL被注解的元素必须是一个URL
@Range被注解的数字或数字的字符串必须在指定的范围内
@Min被注解的元素必须是数字且必须小于等于指定值
@Max被注解的元素必须是数字且必须大于等于指定值
@Past被注解的元索必须是一个过去的日期
@Future被注解的元素必须是一个将来的日期
@AssertTure被注解的元素必须为ture
@AssertFalse被注解的元素必须为false
  1. 在实体类参数前添加@Validated 注解

自定义校验

已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验

  1. 自定义注解State
  2. 自定义校验数据的类StateValidation实现ConstraintValidator接口
  3. 在需要校验的地方使用自定义注解

Mybatis-Plus

使用

  1. 引入依赖

    <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>最新版本</version>
    </dependency>
    
  2. 在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

image-20240216003532363.png

  1. Mapper继承BaseMapper

  2. 实体类添加@TableName@TableId(type = IdType.AUTO)注解

image-20240216003607282.png

  1. 然后就可以使用啦~

    【添加数据:(增)】
        int insert(T entity);              // 插入一条记录
    注:
        T         表示任意实体类型
        entity    表示实体对象
     
    【删除数据:(删)】
        int deleteById(Serializable id);    // 根据主键 ID 删除
        int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);  // 根据 map 定义字段的条件删除
        int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 根据实体类定义的 条件删除对象
        int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量删除
    注:
        id        表示 主键 ID
        columnMap 表示表字段的 map 对象
        wrapper   表示实体对象封装操作类,可以为 nullidList    表示 主键 ID 集合(列表、数组),不能为 nullempty
     
    【修改数据:(改)】
        int updateById(@Param(Constants.ENTITY) T entity); // 根据 ID 修改实体对象。
        int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); // 根据 updateWrapper 条件修改实体对象
    注:
        update 中的 entityset 条件,可以为 nullupdateWrapper 表示实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     
    【查询数据:(查)】
        T selectById(Serializable id); // 根据 主键 ID 查询数据
        List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量查询
        List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据表字段条件查询
        T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据实体类封装对象 查询一条记录
        Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询记录的总条数
        List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合)
        List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合)
        List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(但只保存第一个字段的值)
        <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合),分页
        <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合),分页
    注:
        queryWrapper 表示实体对象封装操作类(可以为 nullpage 表示分页查询条件
    

条件分页

  1. config目录下新建文件MybatisPlusConfig.java
package com.seamon.BigEvent.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOptimizeJoin(true);
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        paginationInnerInterceptor.setOverflow(true);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        return interceptor;
    }

}
  1. ArticleMapper接口中添加一个方法用于查询文章列表。
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
    List<Article> selectArticleList(@Param("pageNum") Integer pageNum, @Param("pageSize") Integer pageSize, @Param("categoryId") Integer categoryId, @Param("state") String state);
}
  1. ArticleServiceImpl中实现ArticleService接口的方法selectArticleList,调用ArticleMapperselectArticleList方法进行查询,并返回结果。
@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleMapper articleMapper;

    @Override
    public List<Article> selectArticleList(Integer pageNum, Integer pageSize, Integer categoryId, String state) {
        // 计算分页查询的偏移量
        Integer offset = (pageNum - 1) * pageSize;
        // 调用ArticleMapper的selectArticleList方法进行查询
        return articleMapper.selectArticleList(offset, pageSize, categoryId, state);
    }
}
  1. ArticleController中添加一个GET请求方法getArticleList,使用@RequestParam注解进行绑定。
@RestController
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    @GetMapping
    public Result<List<Article>> getArticleList(
            @RequestParam Integer pageNum,
            @RequestParam Integer pageSize,
            @RequestParam(required = false) Integer categoryId,
            @RequestParam(required = false) String state) {
        // 调用ArticleService的selectArticleList方法进行查询
        List<Article> articleList = articleService.selectArticleList(pageNum, pageSize, categoryId, state);
        // 构造响应数据并返回
        return Result.success(articleList);
    }
}
  1. 添加ArticleMapper.xml文件,并放置在Mybatis配置的Mapper文件扫描路径下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.seamon.BigEvent.mapper.ArticleMapper">

    <select id="selectArticleList" resultType="com.seamon.BigEvent.entity.Article">
        SELECT *
        FROM article
        <where>
            <if test="categoryId != null">
                AND category_id = #{categoryId}
            </if>
            <if test="state != null">
                AND state = #{state}
            </if>
        </where>
        LIMIT #{pageNum}, #{pageSize}
    </select>

</mapper>

文件上传

String getOriginalFilename(); //获取原始文件名

void transferTo(File dest); //将接收的文件转存到磁盘文件中

long getSize(); //获取文件的大小,单位:字节

byte[] getBytes(); //获取文件内容的字节数组

InputStream getlnputStream(); //获取接收到的文件内容的输入流

小问题

无法解析Maven插件

image-20240131161007312.png 可能是仓库出问题导致下载的包出错,换源后再下载

image-20240131161124532.png

为所有测试样例添加请求头

image-20240131160849372.png

pm.request.addHeader("Authorization:xxx.xxx.xxx");

在用户json中忽略密码

User类中,password上方添加@JsonIgnore,但是注意导包是com.fasterxml.jackson.annotation.JsonIgnore

数据库命名不识别

数据库有数据却不识别到json中

image-20240131173511307.png image-20240131173409290.png application.yml中配置

mybatis:
  configuration:
    map-underscore-to-camel-case: true #开启下划线命名和驼峰命名自动转换

json时间格式不匹配

image-20240215173404091.png

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间

在pojo类中添加注释

分组校验

  1. 定义分组

image-20240215180838281.png 2. 定义校验项时指定归属的分组

image-20240215180824820.png

  1. 校验时指定要校验的分组 image-20240215180913384.png

犯的错误

容易写错

  1. 用@RestController不是@Controller

  2. 要写修饰符private

image-20240215182524553.png

  1. 要求用修饰符static:没注入mapper

image-20240215185708583.png