SpringBoot学习笔记

新建SpringBoot项目

阿里云地址:https://start.aliyun.com

异常消息处理

// 1.自定义异常类,继承RuntimeException
public class MyException extends RuntimeException{
    public MyException() {
    }
}

// 2.定义全局异常类
@RestControllerAdvice
public class GloabExceptionAdvice {
    //异常注解,value是要拦截的异常类,可以自定义,这里为拦截整个异常类
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<Map<String,Object>> exceptionHandler(Exception ex) {
        System.out.println("进入异常消息");
        ex.printStackTrace();
        return Result.res(HttpStatus.INTERNAL_SERVER_ERROR,false,ex.getMessage(),null,new Date());
    }
}

// 3.主动抛出异常
throw new MyException();

异常处理类

//作为springmvc的异常处理器
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
    @ExceptionHandler
    public R doException(Exception ex) {
        //to do
        ex.printStackTrace();//控制台输出错误信息
        return new R("服务器故障,请稍后再试!")
    }
}

消息返回结果类

public class R {
    private Boolean flag;
    private Object data;
    private String msg;
    
    public R() {}
    
    public R(Boolean flag) {
        this.flag = flag;
    }
    
    public R(Boolean flag, Object data) {
        this.flag = flag;
        this.data = data;
    }
    
    public R(String msg) {
        this.msg = msg;
    }
}

表现层统一api返回数据

status - int, 状态码

success - boolean, 结果标识,错误情况下需为 false

message - string, 错误信息

time - datetime, 当前时间

返回json数据

{
    "status": 200,
    "success": true,
    "message": "register successful",
    "data": {
        "nickname ": " Jones "
        },
    "time": "2021-08-23 14:50:28"
}

自定义返回数据类

public class R {
    private int status;
    private Boolean success;
    private Object data;
    private String message;
    private String time;
    
    public R() {}
    
    public R(Boolean success) {
        this.success = success;
    }
    
    public R(int status, Boolean success, String message, Object data, String time) {
        this.status = status;
        this.success = success;
        this.message = message;
        this.data = data;
        this.time = time;
    }
    
    public R(String message) {
        this.success = false;
        this.message = message;
    }
}

基于ResponseEntity的自定义返回

@Data
public class Result{
    private String aaaa;

    public static ResponseEntity<Map<String,Object>> res(HttpStatus status, Boolean success, String message, Object data, Date time) {
        Map<String,Object> map = new HashMap<>();
        map.put("status",status.value());
        map.put("success",success);
        map.put("message",message);
        map.put("data",data);
        map.put("time",time);
        return new ResponseEntity<>(map, status);
    }

    public static ResponseEntity<Map<String,Object>> res(Boolean success, String message, Object data, Date time) {
        return res(HttpStatus.OK,success,message,data,time);
    }

    public static ResponseEntity<Map<String,Object>> res(Boolean success, String message, Object data) {
        return res(HttpStatus.OK,success,message,data,new Date());
    }

    public static ResponseEntity<Map<String,Object>> res(String message, Object data) {
        return res(HttpStatus.OK,true,message,data,new Date());
    }
}

整合MyBatis

1、设置数据源参数

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/databasename?serverTimezone=UTC
    username: root
    password: 123456

2、定义数据层接口与映射配置

@Mapper
public interface UserDao {
    
    @Select("select * from user where id=#{id}")
    public User getById(Integer id);
    
    @Select("select * from user")
    public List<User> getAll();
}

3、XML配置

<?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">
   <!-- 会以namespace 和 id 在mapper 中查询具体的方法
			resultType 表示返回对象的数据类型
			parameterType 表示传入的数据对象
			id  唯一标识,要与命名空间(抽象方法)中定义的方法名称一致
	-->
<mapper namespace="com.banyu.achieve.xml.dao.IUserDao">
    <select id="findAll" resultType="com.banyu.achieve.xml.domain.User">
        select * from user;
    </select>
    <insert id="saveUser" parameterType="com.banyu.achieve.xml.domain.User" >
        <!-- 将自增id存入user对象中 -->
        <selectKey keyColumn="id" resultType="int" order="AFTER" keyProperty="id">
            select last_insert_id()
        </selectKey>
        insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
    </insert>
    <update id="updateUser" parameterType="com.banyu.achieve.xml.dao.IUserDao">
        update user set username =#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id =#{id}
    </update>
    <select id="findUserByName" parameterType="String" resultType="user">
        <!-- 使用这种会有sql  注入风险   select * from user where username like "%${name}%" -->
        <!-- select * from user where username like #{name} -->
        select * from user where username like "%${name}%"
    </select>

    <select id="getTotal" resultType="int" >
        select count(*) from user
    </select>

    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id}
    </delete>

	<select id="findUserById" parameterType="int" resultType="user">
        select * from user where id = #{id}
    </select>
</mapper>

Mybatis-plus

常用注解

@TableName("表名")   //当表名与实体类名不一致时,可以在实体类上加入@TableName()声明
@TableId(type =IdType.AUTO)  //设置为默认主键,方式为自增
@TableId  //声明属性为表中的主键(若属性名称不为默认id)
@TableFieId("字段")  //当实体类属性与表字段不一致时,可以用来声明
@TableLogic   //指定逻辑删除字段

自动填充

//创建时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;

//更新时间 
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Component // 一定不要忘记把处理器加到IOC容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill.....");
        // setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    // 更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill.....");
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

查询

查询单个

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername,user.getUsername());
queryWrapper.eq(User::getPassword,user.getPassword());
User userDB = userMapper.selectOne(queryWrapper);
if (userDB!=null){
    return userDB;
}
throw new RuntimeException("登录失败!!");

查询多个

userMapper.selectList(null);

查询

@Test
public void testSelectById(){
    User user =userMapper.selectById(1);
    System.out.println(user)
}

//测试批量查询
@Test
public void testSelectByBatchId(){
    List<User> user =userMapper.selectBatchIds(Arrays.asList(1,2,3));
    users.forEach(System.out::println)
}

//条件查询
public void testSelectByBatchIds(){
    HashMap<String,Object> map=new HashMap<>();
    //自定义查询
    map.put("name","shuishui");
    map.put("age",3);
    
    List<User> user = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

分页

添加分页拦截器配置

@Configuration
public class MPConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

分页功能实现

@Test
public void getByPage(current) {
    //page对象
    IPage page = new Page(1,5);
    userMapper.selectPage(page,null);
    System.out.println(page.getRecords());  //获取记录
}

JWT

JWT结构

1.Header

{
    "alg": "HS256",
    "typ": "JWT"
}

2.Payload

{
    "name": "admin",
    "admin": true
}

3.Signature

header和payload的base64编码加上密钥

signature = HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

引入JWT

  • maven依赖
<!--引入jwt-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.1</version>
</dependency>

Token生成

public String getToken() {
    //获取时间
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.SECOND,20);  //20秒
    //生成token
    String token = JWT.create()
        .withClaim("userId", "1") //payload
        .withClaim("username", "admin") //注意类型
        .withExpiresAt(instance.getTime()) //令牌时间
        .sign(Algorithm.HMAC256("123key"));//签名

    System.out.println(token);
    return token;
}

Token验证

public Boolean verifyToken(){
    //根据密钥构建验证
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("123key")).build();
    try {
        //验证token,如果验证失败则抛出异常
        DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTExMjI3NTgsInVzZXJJZCI6IjEiLCJ1c2VybmFtZSI6ImFkbWluIn0.4aLHFDuJMJnFWali_3BSryE9ezdj6twj4QZnlwTVWDw");
        //获取token里面的信息
        System.out.println(verify.getClaim("userId").asString());
        System.out.println(verify.getClaim("username").asString());
    }catch (Exception e) {
        //验证失败
        e.printStackTrace();
    }
}

Token异常处理

验证token失败的时候抛出的异常

  • 签名不一致异常

    SignatureVerificationException

  • 令牌过期异常

    TokenExpiredException

  • 算法不匹配异常

    AlgorithmMismatchException

  • 失效的payload异常

    InvalidClaimException

JWTUtil封装

public class JWTUtil {

    //signature密钥
    private static final String SIGN = "123key";

    /**
     * 生成token
     * @param map
     * @return token
     */
    public static String getToken(Map<String,String> map){

        //生成过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);

        JWTCreator.Builder builder = JWT.create();

        map.forEach(( k, v ) -> {
            builder.withClaim(k,v);
        });
        String token = builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SIGN));

        return token;
    }

    /**
     * 验证token并返回token的信息
     * @param token
     * @return token内信息
     */
    public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }
}

Controller逻辑

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("/user/login")
    public ResponseEntity<Map<String,Object>> login(@RequestBody User user, HttpServletResponse response){
        //返回数据的map
        Map<String,Object> map = new HashMap<>();
        //生成token用map
        Map<String,String> payload = new HashMap<>();
        try {
            User userDB = userService.login(user);
            //生成token
            payload.put("userId", String.valueOf(userDB.getId()));
            payload.put("username", userDB.getUsername());
            String token = JWTUtil.getToken(payload);
            map.put("state",true);
            map.put("message","登录成功!!");
            map.put("token",token);
            //设置返回请求头
            response.setHeader("token",token);
        }catch (Exception e){
            //            e.printStackTrace();
            map.put("state",false);
            map.put("message",e.getMessage());
        }

        return new ResponseEntity<>(map, HttpStatus.OK);
    }
    @PostMapping("/shop/test")
    public ResponseEntity<Map<String,Object>> test() {
        Map<String,Object> map = new HashMap<>();
        map.put("state",true);
        map.put("message","请求成功");
        return new ResponseEntity<>(map,HttpStatus.OK);
    }
}

Interceptor拦截器

public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String,Object> map = new HashMap<>();
        String token = request.getHeader("token");
        try {
            DecodedJWT decodedJWT = JWTUtil.verifyToken(token);
            return true; //正常返回true放行
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            map.put("message","无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("message","token过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("message","token算法不一致");
        }catch (Exception e){
            e.printStackTrace();
            map.put("message","无效签名");
        }
        map.put("state",false);
        //jackson转换 map转json
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8"); //注意编码
        response.getWriter().println(json);
        return false; //拦截
    }
}

配置添加拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加过滤规则
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**") //所有路径都拦截
                .excludePathPatterns("/user/**"); //所有用户都放行
    }
}

CORS跨域配置

全局配置

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); //允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); //允许任何请求头
        corsConfiguration.addAllowedMethod("*"); //允许任何请求方式
        source.registerCorsConfiguration("/**",corsConfiguration); //处理所有请求的跨域配置
        return new CorsFilter(source);
    }
}

局部配置

@CrossOrigin //直接加载controller上
@Controller

JJWT

安装maven依赖

<!--引入jjwt,jdk1.8只需引入这个-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成Token

@Test
void getToken() {
    long time = 1000*60*60*24;  //过期时间
    String sign = "123key";  //签名密钥
    JwtBuilder jwtBuilder = Jwts.builder();  //jwt构造器
    String jwtToken = jwtBuilder
        .setHeaderParam("typ","JWT")  //设置header
        .setHeaderParam("alg", "HS256")
        .claim("username","tom")   //设置payload
        .claim("role","admin")
        .setSubject("admin-test")  //设置主题
        .setExpiration(new Date(System.currentTimeMillis()+time))  //设置过期时间,当前系统时间加上24小时
        .setId(UUID.randomUUID().toString())  //设置id
        .signWith(SignatureAlgorithm.HS256,sign)  //签名,对应算法
        .compact();  //组合前面的内容
    System.out.println(jwtToken);
}

验证解析Token

@Test
public void parse() {
    String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InRvbSIsInJvbGUiOiJhZG1pbiIsInN1YiI6ImFkbWluLXRlc3QiLCJleHAiOjE2NTE0MTg1MjQsImp0aSI6ImVhNTA1NjljLTYzYTYtNGEzZC1hNTc3LWI0NDViZDBmOTcwYiJ9.5dRw8edgag5dsRmTBr2XXaUC8RU6nypMBcqOd8ZcuJo";
    String sign = "123key";
    JwtParser jwtParser = Jwts.parser();
    Jws<Claims> claimsJws = jwtParser.setSigningKey(sign).parseClaimsJws(token);
    Claims claims = claimsJws.getBody();
    System.out.println(claims.get("username"));
    System.out.println(claims.get("role"));
    System.out.println(claims.getSubject());
    System.out.println(claims.getExpiration().toLocaleString());
}

自定义异常和404异常

自定义异常类

package com.meteor.exception;

//继承RuntimeException
public class NotFoundException extends RuntimeException{
    //继承父类的有参构造
    public NotFoundException(String message) {
        super(message);
    }
}

全局异常拦截器

@RestControllerAdvice
public class GlobalExceptionResolver {

    @ExceptionHandler(TokenExceptio.class)  //权限校验异常
    public ResponseEntity<Map<String,Object>> tokenExceptionHandler(Exception ex) {
        System.out.println("进入token权限校验异常");
        Map<String,Object> map  = new HashMap<>();
        map.put("message",ex.getMessage());
        map.put("state",401);
        return new ResponseEntity<>(map, HttpStatus.UNAUTHORIZED);
    }

    @ExceptionHandler(NotFoundException.class)  //404自定义异常
    public ResponseEntity<String> notFoundExceptionHandler(Exception ex) {
        System.out.println("进入资源未找到异常");
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)  //用来处理所有异常
    public ResponseEntity<String> exceptionHandler(Exception ex) {
        System.out.println("进入自定义异常");
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

权限拦截器

//验证JWT,继承HandlerInterceptor
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String,Object> map = new HashMap<>();
        String token = request.getHeader("token");
        String msg = "";
        try {
            DecodedJWT decodedJWT = JWTUtil.verifyToken(token);
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            msg = "无效签名"
        }catch (TokenExpiredException e){
            e.printStackTrace();
            msg = "token过期"
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            msg = "token算法不一致"
        }catch (Exception e){
            e.printStackTrace();
            msg = "无效签名"
        }
        throw new TokenExceptio(msg);  //直接抛出自定义异常
    }
}

将拦截器添加到拦截器配置

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())  //自定义拦截器
                .addPathPatterns("/**")  //添加拦截链接
                .excludePathPatterns("/user/**");   //添加排除链接
    }
}

自定义错误异常控制器

@RestController
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class MyErrorController implements ErrorController {

    @RequestMapping
    public ResponseEntity<Map<String,Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        Map<String,Object> map = new HashMap<>();
        map.put("status",status.value());
        if (HttpStatus.NOT_FOUND.equals(status)) {
            map.put("message","请求资源不存在");
        } else if (HttpStatus.UNAUTHORIZED.equals(status)) {
            map.put("message","账号权限不足");
        }
        return new ResponseEntity<>(map,HttpStatus.NOT_FOUND);
    }

    public HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        System.out.println("权限state:" +  statusCode);
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        } else {
            try {
                return HttpStatus.valueOf(statusCode);
            } catch (Exception var4) {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }
        }
    }
}

hutool-all工具包

https://zhuanlan.zhihu.com/p/372359362

引入Hutool

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.6.5</version>
</dependency>

Hibernate.validator

常用注解

注解 释义
@Nul 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内,元素必须为集合,代表集合个数
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内,必须为数组或者字符串,若微数组则表示为数组长度,字符串则表示为字符串长度
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@Pattern(regexp = ) 正则表达式校验
@Valid 对象级联校验,即校验对象中对象的属性

使用步骤

1、在Employee类上加上具体的约束注解。

public class Employee {
   @Null
   private Integer id;
    
   @NotNull
   private Integer parent_id;
   
   @NotBlank
   private String name;

   @PastOrPresent
   private LocalDateTime createtime;
}

2、在controller的类上加上@Validated注解,标注这个类需要校验。在需要校验的参数前面加上@Valid注解,使这个实体类的属性得到校验。

@RestController
@Validated
public class TestController2 {

    @PostMapping("/add")
    public Object add(@RequestBody @Valid Employee employee){
        return "ok";
    }
}

3、Validator的全局异常捕获

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = {VerifyEXception.class, MethodArgumentNotValidException.class})
    public ResponseEntity<Map<String,Object>> verifyEceptionHandler(Exception ex) {
        System.out.println("进入数据校验失败异常");
        //获取valid的message
        String msg = ((MethodArgumentNotValidException) ex).getFieldError().getDefaultMessage();
        return Result.res(HttpStatus.UNPROCESSABLE_ENTITY,false,msg,null,new Date());
    }
}

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity handleBindException(MethodArgumentNotValidException ex) {
    FieldError fieldError = ex.getBindingResult().getFieldError();
    log.warn("参数校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());
    return ResponseEntity.ok(Response.fail(211,fieldError.getDefaultMessage(),fieldError.getDefaultMessage()));
}

Get请求

@RestController
@RequestMapping("/test")
@Validated
public class TestController {

    /**
     * 测试校验框架返回结果格式
     */
    @GetMapping(value = "/validator2")
    public String testValidator2(@NotBlank(message = "姓名不能为空") String name){
        return "校验成功...";
    }

}

Annotation

权限注解,用于Contoller方法上,在拦截器preHandle中进行权限校验,原理是获取方法上的权限注解,根据注解的值进行校验。

@Target(ElementType.METHOD)				// 该注解可作用的目标
@Retention(RetentionPolicy.RUNTIME)		// 作用时机
public @interface AuthCheck {

    String value() default "";  // 默认为空

}

校验注解

private Optional<AuthCheck> getAuthCheck(Object handler) {
    if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        AuthCheck authCheck = handlerMethod.getMethod().getAnnotation(AuthCheck.class);
        if (authCheck == null) {
            return Optional.empty();
        }
        return Optional.of(authCheck);
    }
    return Optional.empty();
}

调用方法

Optional<AuthCheck> authCheck = this.getAuthCheck(handler);
if (!authCheck.isPresent()) {
    return true;
}

ThreadLocal

一、ThreadLocal简介

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

  ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示:

img

ThreadLocal常用方法:

  • public void set(T value) 设置当前线程的线程句不变量的值
  • public T get() 返回当前线程所对应的线程局部变量的值

二、Springboot应用ThreadLocal

使用ThreadLocal保存用户信息

客户端发送的每次Http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中设计到的后端代码都属于同一个线程,可以通过一下代码获取线程ID.

Thread.currentThread().getId();  //获取当前线程ID

我们可以配置拦截器,通过preHandle方法中的header来获取User信息,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户信息User),然后就能在后续的Controller方法中通过ThreadLocal的get方法来获取到用户信息(可实现权限控制)

编写User实体类

@Data
public class User() {
    private Long id;
    private String username;
    private String password;
    private Integer type;     //用户类型,用于权限校验
}

编写AuthUser类,封装ThreadLocal来操作用户数据

public class AuthUser {
    private static final ThreadLocal<User> threadLocal = new ThreadLocal() ;

    public static User get() {
        return threadLocal.get();
    }

    public static void set(User value) {
        threadLocal.set(value);
    }

    public static void remove() {
        threadLocal.remove();
    }

    public static Long getUserId() {
        try {
            return threadLocal.get().getUid();
        } catch (Exception e) {
            throw new TokenCheckException("铁子,请先登录再操作");
        }
    }

}

Redis

基本操作

//增
redisTemplate.opsForValue().put("key","value");
//删
redisTemplate.delete(key);
//改
redisTemplate.opsForValue().set("key","value");
//查
redisTemplate.opsForValue().get("key");
//是否存在
redisTemplate.hasKey(key);
//设置过期时间
redisTemplate.expire(key, timeout, unit);
redisTemplate.expireAt(key, date);

使用redis实现登录

登录成功将用户信息存入redis

redis工具类

@Component
@SuppressWarnings({"unchecked", "all"})
public class RedisUtils {
    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
    private RedisTemplate<String, String> redisTemplate;

    public RedisUtils(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 指定缓存失效时间
     *
     * @param key      键
     * @param time     时间(秒)
     * @param timeUnit 单位
     */
    public boolean expire(String key, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, timeUnit);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 根据 key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 查找匹配key
     *
     * @param pattern key
     * @return /
     */
    public List<String> scan(String pattern) {
        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>();
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 分页查询 key
     *
     * @param patternKey key
     * @param page       页码
     * @param size       每页数目
     * @return /
     */
    public List<String> findKeysForPage(String patternKey, int page, int size) {
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>(size);
        int tmpIndex = 0;
        int fromIndex = page * size;
        int toIndex = page * size + size;
        while (cursor.hasNext()) {
            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
                result.add(new String(cursor.next()));
                tmpIndex++;
                continue;
            }
            // 获取到满足条件的数据后,就可以退出了
            if (tmpIndex >= toIndex) {
                break;
            }
            tmpIndex++;
            cursor.next();
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void del(String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                boolean result = redisTemplate.delete(keys[0]);
                log.debug("--------------------------------------------");
                log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
                log.debug("--------------------------------------------");
            } else {
                Set<String> keySet = new HashSet<>();
                for (String key : keys) {
                    keySet.addAll(redisTemplate.keys(key));
                }
                long count = redisTemplate.delete(keySet);
                log.debug("--------------------------------------------");
                log.debug("成功删除缓存:" + keySet.toString());
                log.debug("缓存删除数量:" + count + "个");
                log.debug("--------------------------------------------");
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public String get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 批量获取
     *
     * @param keys
     * @return
     */
    public List<Object> multiGet(List<String> keys) {
        List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));
        List resultList = Lists.newArrayList();
        Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add)));
        return resultList;
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, String value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key      键
     * @param value    值
     * @param time     时间
     * @param timeUnit 类型
     * @return true成功 false 失败
     */
    public boolean set(String key, String value, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);

    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, String value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, String value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, String... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<String> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, String value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, String... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, String... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<String> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, String value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, String value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<String> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<String> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return /
     */
    public boolean lUpdateIndex(String key, long index, String value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * @param prefix 前缀
     * @param ids    id
     */
    public void delByKeys(String prefix, Set<Long> ids) {
        Set<String> keys = new HashSet<>();
        for (Long id : ids) {
            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
        }
        long count = redisTemplate.delete(keys);
        // 此处提示可自行删除
        log.debug("--------------------------------------------");
        log.debug("成功删除缓存:" + keys.toString());
        log.debug("缓存删除数量:" + count + "个");
        log.debug("--------------------------------------------");
    }
}

热门相关:有个人爱你很久   最强反套路系统   最强装逼打脸系统   法医王妃不好当!   特工重生:快穿全能女神