新建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">
-
- <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" >
-
- <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">
-
-
- 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
- <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依赖
- <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变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示:

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("--------------------------------------------");
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |