知识点
JWT-令牌工具
组成
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz
使用
-
引入java-jwt坐标
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency>
-
导入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(); } }
-
调用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);
-
解析令牌,抛出异常,说明令牌被篡改或过期了
// 令牌认证 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被改动,验证不一致)
拦截器-身份校验
自定义拦截器
- 继承HandlerInterceptor接口
- 重写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: 请求(接口)结束后执行
在配置文件中注册拦截器
- 继承WebMvcConfigurer接口
- 注入拦截器
- 重写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存储的数据,线程安全
- 在同一个线程间共享数据
使用
- 导入工具类
/**
* 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();
}
}
- 在拦截器中调用set方法存入数据
3. 在需要获取数据的位置调用get方法
-
释放内存
@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参数为正则表达式
校验类的属性值
- 在实体类的属性前添加以下注解
注解 | 作用 |
---|---|
@Pattern | 必须符合给定的正则表达式 |
@Null | 被注解的元索必须为空 |
@NotNull | 值不能为null |
@NotEmpty | 值不能为null,且内容不为空 |
满足邮箱格式 | |
@URL | 被注解的元素必须是一个URL |
@Range | 被注解的数字或数字的字符串必须在指定的范围内 |
@Min | 被注解的元素必须是数字且必须小于等于指定值 |
@Max | 被注解的元素必须是数字且必须大于等于指定值 |
@Past | 被注解的元索必须是一个过去的日期 |
@Future | 被注解的元素必须是一个将来的日期 |
@AssertTure | 被注解的元素必须为ture |
@AssertFalse | 被注解的元素必须为false |
- 在实体类参数前添加
@Validated
注解
自定义校验
已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验
- 自定义注解State
- 自定义校验数据的类StateValidation实现ConstraintValidator接口
- 在需要校验的地方使用自定义注解
Mybatis-Plus
使用
-
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>最新版本</version> </dependency>
-
在 Spring Boot 启动类中添加
@MapperScan
注解,扫描 Mapper 文件夹
-
Mapper继承BaseMapper
-
实体类添加
@TableName
和@TableId(type = IdType.AUTO)
注解
-
然后就可以使用啦~
【添加数据:(增)】 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 表示实体对象封装操作类,可以为 null。 idList 表示 主键 ID 集合(列表、数组),不能为 null 或 empty 【修改数据:(改)】 int updateById(@Param(Constants.ENTITY) T entity); // 根据 ID 修改实体对象。 int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); // 根据 updateWrapper 条件修改实体对象 注: update 中的 entity 为 set 条件,可以为 null。 updateWrapper 表示实体对象封装操作类(可以为 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 表示实体对象封装操作类(可以为 null) page 表示分页查询条件
条件分页
- 在
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;
}
}
- 在
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);
}
- 在
ArticleServiceImpl
中实现ArticleService
接口的方法selectArticleList
,调用ArticleMapper
的selectArticleList
方法进行查询,并返回结果。
@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);
}
}
- 在
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);
}
}
- 添加
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插件
可能是仓库出问题导致下载的包出错,换源后再下载
为所有测试样例添加请求头
pm.request.addHeader("Authorization:xxx.xxx.xxx");
在用户json中忽略密码
在User
类中,password
上方添加@JsonIgnore
,但是注意导包是com.fasterxml.jackson.annotation.JsonIgnore
数据库命名不识别
数据库有数据却不识别到json中
application.yml中配置
mybatis:
configuration:
map-underscore-to-camel-case: true #开启下划线命名和驼峰命名自动转换
json时间格式不匹配
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间
在pojo类中添加注释
分组校验
- 定义分组
2. 定义校验项时指定归属的分组
- 校验时指定要校验的分组
犯的错误
容易写错
-
用@
RestController
不是@Controller
-
要写修饰符
private
- 要求用修饰符static:没注入mapper