当前位置: 首页 > news >正文

Jwt(Json web token)——从Http协议到session+cookie到Token Jwt介绍 Jwt的应用:登陆验证的流程

目录

  • 引出
  • 从Http协议到session&cookie到Token
    • HTTP协议
    • session & cookie
      • session
      • cookie
      • 为什么需要session & cookie?
    • JavaEE传统解决长连接方案
    • 问题:分布式不适用
    • 解决方案:令牌Token
  • Jwt,Json web token
    • jwt的结构
    • Header
      • 加密算法
      • Base64 + 图片编码
    • Payload 载荷:比如唯一的用户ID
      • Registered claims
      • Public claims
      • Private claims
      • Payload案例
    • Signature:签名
  • jwt使用初步
    • 导包
    • 生成token
    • 解析token
    • 获取token的内容
    • 验证token
    • 几种token验证的情况
      • token的过期时间
      • token的内容被改过
      • token的载荷被更改
  • Jwt的应用:登陆验证的流程
    • 用户表
    • dao数据库
    • service业务
    • 定义异常
      • 异常的拦截处理
    • controller层
      • swagger测试
    • 使用拦截器统一拦截方式
    • Vo对象给前端
    • DTO对象
      • 日期json问题
      • 枚举类型的json化
    • application.yml配置文件
    • pom文件
  • 总结

引出


1.http协议的特点,无状态,被动请求;
2.session+cookie解决浏览器的无状态问题,但是分布式不适用;
3.Jwt,Json web token,token令牌入门,头部+载荷+签名;
4.Jwt的使用初步,如果过期,报错com.auth0.jwt.exceptions.TokenExpiredException;
5.应用:登陆验证的流程;

从Http协议到session&cookie到Token

HTTP协议

特点

  • 无记忆性(无状态)

  • 请求响应

    短连接

    被动请求

session & cookie

session

Session: Session存在服务器端的对象。

cookie

Cookie就是存储在浏览器的一个对象(客户端)

为什么需要session & cookie?

本质上讲 : http协议的问题(无记忆性)。

在这里插入图片描述

JavaEE传统解决长连接方案

在这里插入图片描述

在这里插入图片描述

问题:分布式不适用

  • 只适合于单体架构,集群/分布式架构的不合适
    • Session要进行同步,比较困难(网络抖动、延迟等),有状态的
    • Cookie: 不支持跨域,前后端分离
    • 移动端(很少使用cookie),比如微信小程序不支持cookie

session需要同步

在这里插入图片描述

  • 对于多服务器集群,每台服务器都要读取Session,共享困难。

在这里插入图片描述

解决方案:令牌Token

在这里插入图片描述

加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:

  • 对称加密,如AES
    • 基本原理:将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
    • 优势:算法公开、计算量小、加密速度快、加密效率高
    • 缺陷:双方都使用同样密钥,安全性得不到保证
  • 非对称加密,如RSA
    • 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
      • 私钥加密,持有私钥或公钥才可以解密
      • 公钥加密,持有私钥才可解密
    • 优点:安全,难以破解
    • 缺点:算法比较耗时
  • 不可逆加密,如MD5,SHA
    • 基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。

RSA算法历史:

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA

目前流行的还有oauth2

Jwt,Json web token

JSON Web Token(令牌)

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact压缩的 and self-contained自包含的 way for securely transmitting information安全传输 between parties as a JSON object。

This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

  • 基于JSON格式用于网络传输的令牌。
  • 紧凑的Claims声明格式。
  • Claim有索赔、声称、要求或者权利要求的含义。

jwt的结构

在这里插入图片描述

官网:https://jwt.io/

在这里插入图片描述

如果改前面的,最后的签名会改变,就知道被改动过了

在这里插入图片描述

Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据。

在这里插入图片描述

加密算法

  • HSA256

    SHA(Secure Hash Algorithm,安全散列算法)是一个密码散列函数家族,由美国国家安全局(NSA)设计,并由美国国家标准与技术研究院(NIST)发布,是美国的政府标准。

    • 无论输入多长,都输出64个字符,共32字节(byte),256位(bit)
    • 输出只包含数字0`9`和字母`A`F,大小写不敏感
  • RSA256

    由美国麻 省理工 学院三 位学者 Riv est、Sh amir 及Adleman 研 究发 展出 一套 可实 际使用 的公 开金 钥密码系 统,那 就是RSA(Rivest-Shamir-Adleman)密码系统。

    RS256(带有SHA-256的 RSA 签名)是一种非对称算法,它使用公钥/私钥对:身份提供者拥有用于生成签名的私钥(秘密)密钥,而 JWT 的消费者获得公钥验证签名。由于与私钥相反,公钥不需要保持安全,因此大多数身份提供者都可以让消费者轻松获取和使用(通常通过元数据 URL)。

Base64 + 图片编码

在这里插入图片描述

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。Base64不是加密算法,只是一种编码方式,可以用Base64来简单的“加密”来保护某些数据,所以每 6 个比特为一个单元,对应某个可打印字符。

在这里插入图片描述

“hello” 编码

h—01101000 e —01100101

在这里插入图片描述

在这里插入图片描述

package com.tianju.redisDemo.jwt;import org.apache.ibatis.logging.stdout.StdOutImpl;import java.util.Base64;public class JwtDemo {public static void main(String[] args) {String s = "h";// hz转成2进制:h-->ASCII值(10进制)-->2进制String bin = "0" + Integer.toBinaryString(s.charAt(0));System.out.println("h转换成2进制:"+bin);System.out.println("截取前6位:"+bin.substring(0, 6));// 进行base64编码String sBase64 = new String(Base64.getEncoder().encode(s.getBytes()));System.out.println(sBase64);}
}

Base64 在线编码解码 | Base64 加密解密 - Base64.us

在这里插入图片描述

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<img src="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAsLTD/CwsJ/xoeIf82QUn/Njw5/0ZGRf9LTFH/WVZX/1JVV/9iZmT/ZmNx/1dha/85Qkz/Jyct/w0HBf8OCgf/Cw0P/yEhIv87O0D/RjpI/0tBRv8uLCj/FxsS/x0eGP8bFhn/IyAl/y8xMv9xd3r/eoCJ/0NFVP8gHSX/CAUC/xQXG/8nKC//OjY4/z86Nv8RDRv/AAAZ/wAAGf8UEyL/MyEg/w8AAP8FAAD/DAgH/2hobv+FhZH/MzVA/xUSEv8+QUb/Ly43/ykiK/8BAT3/BARi/wEBaP8BAFj/LCRk/2NCO/80AAH/LAMD/x0BAf8BAAD/PDk+/2pucv82MjX/SUdY/0EvJ/8KCTH/BwWT/wcEpv8CAJr/AAF6/y0ue/+HSEz/XQAA/1EDB/8+Awb/HAEB/wgAAP9nZmr/Yl5l/01OUP8uJiX/AwN5/wMFzP8FCMj/Awa//wAArP8sMJX/lU9R/34AAP96Awb/VwME/y0EAP8XAAD/MCYp/3t6gP9cVk//JSdE/wECyP8ABu7/AQDx/wEA4v8AAMH/LTG4/6NcW/+WAAD/oQAC/5IDAf9UBQD/NwMA/x0EBf92eX7/eWNi/yQkY/8BAu3/AQD9/xwk6P9HUPD/EhPi/y4z1v+tZGD/nAAA/7UDA//RCAb/fgUB/1ICAf8mAAD/goiK/4RuZv8kHnf/AgH9/wAA9v9gce7/9//5/7+++P+Ji+3/qGZq/6MAAP+oAgn/rQcL/4YEBP9aAwH/KAEB/4+Kj/96dm3/IB5X/wYB+P8BAP3/HyXv/8bN9f//29vt/6teZv+dAAH/iwUA/3MCAf9gAwP/PgQD/xcAAP90bnX/Zm5y/yUiNP8GBcT/AgT//wAA/f8vL/L/eHfx/3J37P+tb2v/qwEA/5oBA/9sAwH/UAMD/ywBAf8mEhn/b212/11ugf9aR1D/AQFg/wUG9v8EAvz/BwD6/wAA5f8wNNT/pG1s/54AAP+NBAP/YwQB/zoCAv8NAAD/T01X/05OWP9Nb4T/Xl9f/0M1TP8AAJz/CArl/wIE6f8AALz/ODq0/7Jwbv93AAD/bgQD/0gEAP8WAAD/GxYZ/15aaP8pLDj/X5bB/2iHnP97c3n/KSg4/wMAfv8EAL7/AACc/zczoP+fYWb/YgAA/1EBAf8WAAD/HxMW/2hpdf87R1n/MTM9/zRtnf+Pxev/gpek/2pjZP9gX2z/JCNW/wAATf8bG1X/VT1C/xoAAP8gDg7/PDQ4/1pNVv9QSVr/SEtd/xIRGP9McYj/R3uU/4a+2f9Wbn//SVFQ/2lmXv9oYV//YlVd/2VVVP9eVlf/WFlf/0dKVv8zQUj/Q0xU/yAYIP8CAAT/gABseQAAMyAAAGRlAAAyNAAAdGUAAGluAAAxLAAAdXQAAG50AABBTQAAUFIAAEVTAABSXwAAVkUAADIzgAFSTw=="
style="width: 100px" height="100px">
</body>
</html>

Payload 载荷:比如唯一的用户ID

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

Payload 也使用BASE64编码

Registered claims

这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等

序号名称解释
1iss (issuer)签发人
2exp (expiration time)过期时间
3sub (subject)主题
4aud (audience)受众(接收jwt的一方)
5nbf (Not Before)生效时间
6iat (Issued At)签发时间
7jti (JWT ID)jwt的唯一身份标识,
主要用来作为一次性token,
从而回避重放攻击

Public claims

这些可以由使用JWT的人员随意定义。

Private claims

这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。

Payload案例

{  "sub": "456781234",  "name": "Mr.zhang",  "admin": true}

Signature:签名

The signature is used to verify the message wasn’t changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

Signature 部分是对前两部分的签名,防止数据篡改。

https://jwt.io/

Signature部分由编码后的Header、Payload和自定义的秘钥使用Header中指定的算法(HSA256)进行加密签名;

在这里插入图片描述

结构

在这里插入图片描述

jwt使用初步

在这里插入图片描述

导包

<!--        jwt的依赖--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version></dependency>

生成token

package com.tianju.redisDemo.jwt;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JavaJwtTest {public static void main(String[] args) {// 定义头部headerMap<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 私钥String salt = "pet";String token = JWT.create().withHeader(header).withClaim("username", "tom").withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60)) // 60s之后过期.sign(Algorithm.HMAC256(salt.getBytes()));System.out.println(token);}
}

获得token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODk3NzY0MTUsInVzZXJuYW1lIjoidG9tIn0.kPmgw03j9g3EJAo2LLzXHd1b7H3uF5zF-0EDEaeonMY

在这里插入图片描述

解析token

DecodedJWT decode = JWT.decode(token);

获取token的内容

System.out.println("Header: " + decode.getHeader());
System.out.println("Payload: " + decode.getPayload());
System.out.println("Audience: " + decode.getAudience());
System.out.println("Signature: " + decode.getSignature());

验证token

JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();

获取信息

System.out.println(build.verify(token).getClaim("data"));

{“name”:”tomcat”,”username”:”tom”}

几种token验证的情况

token的过期时间

com.auth0.jwt.exceptions.TokenExpiredException

package com.tianju.redisDemo.jwt;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JavaJwtTest1 {public static void main(String[] args) throws InterruptedException {// 定义头部headerMap<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 私钥String salt = "pet";String token = JWT.create().withHeader(header).withClaim("username", "tom").withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 3)) // 60s之后过期.sign(Algorithm.HMAC256(salt.getBytes()));System.out.println(token);Thread.sleep(1000*5);JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();DecodedJWT verify = jwtVerifier.verify(token);System.out.println(verify.getClaim("username"));}
}

在这里插入图片描述

token的内容被改过

com.auth0.jwt.exceptions.JWTDecodeException

package com.tianju.redisDemo.jwt;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JavaJwtTest1 {public static void main(String[] args) throws InterruptedException {// 定义头部headerMap<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 私钥String salt = "pet";String token = JWT.create().withHeader(header).withClaim("username", "tom").withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 3)) // 60s之后过期.sign(Algorithm.HMAC256(salt.getBytes()));System.out.println(token);token = token.replaceAll("[a-zA-Z]","X");JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();DecodedJWT verify = jwtVerifier.verify(token);System.out.println(verify.getClaim("username"));}
}

在这里插入图片描述

token的载荷被更改

在这里插入图片描述

在这里插入图片描述

package com.tianju.redisDemo.jwt;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JavaJwtTest1 {public static void main(String[] args) throws InterruptedException {// 定义头部headerMap<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 私钥String salt = "pet";String token = JWT.create().withHeader(header).withClaim("username", "tom").withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) //.sign(Algorithm.HMAC256(salt.getBytes()));System.out.println("登陆成功产生token:"+token);//        token = token.replaceAll("[a-zA-Z]","X");String hackToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODk3Nzk1MjMsInVzZXJuYW1lIjoicGV0In0.laRjGZsM5nq8IYPBMSYCbMdxyFMiXaZXKg9F7WL-n-Q";System.out.println("被改过的token:"+hackToken);JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("pet")).build();DecodedJWT verify = jwtVerifier.verify(hackToken);System.out.println(verify.getClaim("username"));System.out.println("签名:"+ verify.getSignature());}
}

Jwt的应用:登陆验证的流程

在这里插入图片描述

整体的流程:

第一步:产生30分钟有效时间的token;

第二步:在redis里面存储token,redis里面有效时间为60分钟;

第三步:token过期,但是Redis里面的token还没有过期,此时进行续期;

第四步:给原有的token续期,需要设置最大续期时间,目前用最大续期次数解决;

用户表

用户名、密码、电话号码、邮箱

在这里插入图片描述

user.java实体类

package com.tianju.redisDemo.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user_owner")
public class User {@TableId(value = "id",type = IdType.AUTO)private Integer id;@TableField("username")private String username;@TableField("realname")private String realName;@TableField("password")private String password;@TableField("tel")private String tel;@TableField("security_key")private String securityKey;@TableField("create_time")private Date createTime;
}

dao数据库

UserMapper.java文件

package com.tianju.redisDemo.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tianju.redisDemo.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {}

service业务

处理登陆业务:抛出异常:

(1)throw new UsernameIsEmptyException(“输入为空异常”);

(2)throw new UsernameNotFoundException(“用户名不存在异常”); 后面用布隆过滤器

(3)throw new UsernameOrPasswordErrorException(“用户名或者密码错误异常”);

package com.tianju.redisDemo.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tianju.redisDemo.dao.UserMapper;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.exception.UsernameIsEmptyException;
import com.tianju.redisDemo.exception.UsernameNotFoundException;
import com.tianju.redisDemo.exception.UsernameOrPasswordErrorException;
import com.tianju.redisDemo.service.IUserService;
import com.tianju.redisDemo.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;@Service
@Transactional
@Slf4j
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic List<UserVo> queryAll() {List<UserVo> userVolist = new LinkedList<>();List<User> userList = userMapper.selectList(null);for (User user : userList) {userVolist.add(new UserVo(user.getUsername(), user.getRealName(), user.getCreateTime()));}return userVolist;}/*** 登陆的业务* @param username* @param password*/@Overridepublic User login(String username, String password) {if (username==null || password==null||"".equals(username)||"".equals(password)){// 抛出异常,USERNAME_IS_EMPTY_EXCEPTIONthrow new UsernameIsEmptyException("输入为空异常");}// 判断用户名是否存在LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getUsername,username);User user = userMapper.selectOne(lambdaQueryWrapper);if (Objects.isNull(user)){// 用户名不存在// 抛出异常:USERNAME_NOT_FOUND_EXCEPTIONthrow new UsernameNotFoundException("用户名不存在异常");}if (!user.getPassword().equals(password)){// 密码不对// 抛出异常:USERNAME_OR_PASSWORD_ERROR_EXCEPTIONthrow  new UsernameOrPasswordErrorException("用户名或者密码错误异常");}log.debug("用户名{}登陆系统成功",username);return user;}
}

定义异常

在这里插入图片描述

在这里插入图片描述

package com.tianju.redisDemo.exception;/*** 非法用户*/
public class IllegalUserException extends RuntimeException{public IllegalUserException(String message) {super(message);}
}
package com.tianju.redisDemo.exception;/*** 用户名。或者密码未输入异常*/
public class UsernameIsEmptyException extends RuntimeException{public UsernameIsEmptyException(String message) {super(message);}
}
package com.tianju.redisDemo.exception;/*** 用户未登录*/
public class UserNotLoginException extends RuntimeException{public UserNotLoginException(String message) {super(message);}
}

异常的拦截处理

controller层调用service时,会抛出异常,捕获controller层的异常,进行处理,返回响应

package com.tianju.redisDemo.exception.handle;import com.auth0.jwt.exceptions.TokenExpiredException;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import com.tianju.redisDemo.exception.*;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.Date;/*** 拦截controller层的异常,捕获异常,进行处理;* 用户登陆异常*/
@RestControllerAdvice
public class UserExceptionHandler {@ExceptionHandler(UsernameIsEmptyException.class)public HttpResp usernameIsEmptyHandler(UsernameIsEmptyException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}@ExceptionHandler(UsernameNotFoundException.class)public HttpResp usernameNotFoundHandler(UsernameNotFoundException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}@ExceptionHandler(UsernameOrPasswordErrorException.class)public HttpResp usernameOrPasswordErrorHandler(UsernameOrPasswordErrorException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}@ExceptionHandler(UserNotLoginException.class)public HttpResp userNotLoginHandler(UserNotLoginException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}@ExceptionHandler(IllegalUserException.class)public HttpResp illegalUserHandler(IllegalUserException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}//    @ExceptionHandler(TokenExpiredException.class)
//    public HttpResp tokenExpiredHandler(TokenExpiredException e){
//        return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());
//    }@ExceptionHandler(UserNeedRenewTokenException.class)public HttpResp userNeedRenewTokenHandler(UserNeedRenewTokenException e){return new HttpResp(ResultCode.USER_LOGIN_ERROR,new Date(),e.getMessage());}
}

在这里插入图片描述

controller层

UserController.java文件

package com.tianju.redisDemo.controller;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Api(tags = "用户Api")
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate IUserService userService;@Value("${lifespanToken}")private Integer lifespanToken;@Value("${lifespanRedis}")private Integer lifespanRedis;@ApiOperation(value = "login",notes = "用户登陆api")@ApiImplicitParams({@ApiImplicitParam(name = "username",required = true),@ApiImplicitParam(name = "password",required = true)})@PostMapping("/login")public HttpResp login(String username, String password, HttpServletResponse response,HttpServletRequest request){User user = userService.login(username, password);// 这里有可能出现异常// 能走到这一步,说明登陆成功,产生token// 第一步:产生30分钟有效时间的tokenString securityKey = user.getSecurityKey();String token = createToken(username, securityKey);// 存入redis里面,用户名和秘钥,以token为键,所以只要token被改动,就会认为非法用户stringRedisTemplate.opsForHash().put(token, "username", username);stringRedisTemplate.opsForHash().put(token, "securityKey", securityKey);// redis过期晚一点,双token机制// 第二步:在redis里面存储token,redis里面有效时间为60分钟;stringRedisTemplate.expireAt(token, new Date(System.currentTimeMillis()+lifespanRedis));// 3.token要给前端response.addHeader("bm_token",token);// 在session里面存续期的次数request.getSession().setAttribute("renewTokenTimes", 0);return HttpResp.results(ResultCode.USER_LOGIN_SUCCESS,new Date(),token);}/*** 产生token对象* @param username 用户名* @param securityKey 安全码* @return*/private String createToken(String username,String securityKey){Map<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 链式写法return JWT.create().withHeader(header).withClaim("username", username)
//                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 3)) // 60*30// 第一步:产生30分钟有效时间的token.withExpiresAt(new Date(System.currentTimeMillis() + lifespanToken)) // 60*30.sign(Algorithm.HMAC256(securityKey.getBytes()));}@ApiOperation(value = "findAll",notes = "用户登陆api")@GetMapping("/findAll")public HttpResp findAll(){// 使用拦截器处理没有token的情况return HttpResp.results(ResultCode.USER_FIND_SUCCESS,new Date(),userService.queryAll());}
}

swagger测试

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用拦截器统一拦截方式

AuthInterceptor.java文件
TokenExpiredException异常

在这里插入图片描述

package com.tianju.redisDemo.interceptor;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tianju.redisDemo.exception.IllegalUserException;
import com.tianju.redisDemo.exception.UserNeedRenewTokenException;
import com.tianju.redisDemo.exception.UserNotLoginException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${lifespanToken}")private Integer lifespanToken;@Value("${lifespanRedis}")private Integer lifespanRedis;@Value("${MAX_RENEW_TOKEN_TIME}")private Integer MAX_RENEW_TOKEN_TIME;// 最大续期次数@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("bm_token");if (token==null || "".equals(token)){throw new UserNotLoginException("用户没有登陆");}// 如何获取tokenString securityKey = (String) stringRedisTemplate.opsForHash().get(token, "securityKey");if (securityKey==null){throw new IllegalUserException("非法用户");}// 过期异常,篡改异常JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(securityKey)).build();try {// 如果token过期,会抛出com.auth0.jwt.exceptions.TokenExpiredExceptionDecodedJWT decodedJWT = jwtVerifier.verify(token);} catch (TokenExpiredException e) { // token过期,redis没有过期// 第三步:token过期,但是Redis里面的token还没有过期,此时进行续期String username = (String) stringRedisTemplate.opsForHash().get(token, "username");// 方案一:产生新的token给前端;
//            String newToken = removeTokenMakeNewToken(token, username, securityKey);
//            response.addHeader("bm_token",token);// 方案二:给原有的token续期,需要设置最大续期时间Integer renewTokenTimes = (Integer) request.getSession().getAttribute("renewTokenTimes");request.getSession().setAttribute("renewTokenTimes",renewTokenTimes+1);// 产生一个新的token,更新redislog.info( ">>>>>>>>>"+username+"的token过期,即将进行续期,续期次数:"+renewTokenTimes);if (renewTokenTimes<MAX_RENEW_TOKEN_TIME){System.out.println("我输续期");stringRedisTemplate.expireAt(token, new Date(System.currentTimeMillis()+lifespanRedis));}else {System.out.println("不再续期");throw new UserNeedRenewTokenException("达到最大续期次数,需要重新登陆");}}return true;}private String removeTokenMakeNewToken(String token,String username,String securityKey){// 1.产生新的tokenMap<String,Object> header = new HashMap<>();header.put("alg", "HS256");// 链式写法String newToken = JWT.create().withHeader(header).withClaim("username", username)
//                    .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 3)) // 60*30.withExpiresAt(new Date(System.currentTimeMillis() + lifespanToken)) // 60*30.sign(Algorithm.HMAC256(securityKey.getBytes()));// 2.更新redisstringRedisTemplate.opsForHash().delete(token,"*"); // 删除之前的tokenstringRedisTemplate.opsForHash().put(newToken, "username", username);stringRedisTemplate.opsForHash().put(newToken, "securityKey", securityKey);// redis过期晚一点,双token机制stringRedisTemplate.expireAt(newToken, new Date(System.currentTimeMillis()+lifespanRedis));// 3.新的token还要给前端return newToken;
//        response.addHeader("bm_token",token);}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1VGKnaD-1690192090095)(D:\javalearn\思维导图笔记\mdPictures\image-20230724173507503.png)]

配置拦截器 @Configuration

package com.tianju.redisDemo.config;import com.tianju.redisDemo.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class BmMvcConfig implements WebMvcConfigurer {@Resourceprivate AuthInterceptor authInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/user/login");}
}

Vo对象给前端

只给前端部分数据信息

package com.tianju.redisDemo.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVo {private String username;private String name;private Date createTime;
}

DTO对象

日期json问题

@JsonFormat(timezone = “GMT+8”)

HttpResp.java文件

package com.tianju.redisDemo.dto;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;import java.io.Serializable;
import java.util.Date;/*** 返回给前端的响应* @param <T>*/
@ApiModel("DTO返回数据")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpResp<T> implements Serializable {private ResultCode resultCode;@ApiModelProperty("time")@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",timezone = "GMT+8")private Date time;@ApiModelProperty("results")private T result;public static <T> HttpResp <T> results(ResultCode resultCode,Date time,T results){HttpResp httpResp = new HttpResp();httpResp.setResultCode(resultCode);httpResp.setTime(time);httpResp.setResult(results);return httpResp;}
}

枚举类型的json化

@JsonFormat(shape = JsonFormat.Shape.OBJECT)

@Getter

ResultCode.java枚举类

package com.tianju.redisDemo.dto;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;/*** 枚举类型,http请求的返回值*/
// 枚举类型的json化,需要有get方法
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@Getter
public enum ResultCode {BOOK_RUSH_SUCCESS(20010,"图书抢购成功"),BOOK_RUSH_ERROR(3001,"图书抢购失败"),LUA_SCRIPT_ERROR(3002,"Lua脚本操作失败"),USER_FIND_ERROR(40010,"非法请求,布隆过滤器不通过"),USER_FIND_SUCCESS(20010,"查询用户名成功"),USER_LOGIN_ERROR(40030,"用户登陆失败"),USER_LOGIN_SUCCESS(20020,"用户登陆成功"),;@ApiModelProperty("状态码")private Integer code;@ApiModelProperty("提示信息")private String msg;private ResultCode(Integer code,String msg){this.code =code;this.msg = msg;}
}

application.yml配置文件

server:port: 9099# token的过期时间是30分钟;redis的过期时间是90分钟
lifespanToken: 6000 # 1000*60*30
lifespanRedis: 20000 # 1000*60*90
MAX_RENEW_TOKEN_TIME: 1 # 最大续期次数spring:# redis的相关配置redis:host: localhostport: 6379database: 0# mysql的相关配置datasource:druid:url: jdbc:mysql://127.0.0.1/community?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: 123driver-class-name: com.mysql.cj.jdbc.Driver# 日志需要配置一下
logging:level:com.tianju.redisDemo: debug# mybatis-plus的日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# knife需要允许
knife4j:enable: true

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.tianju.jwt</groupId><artifactId>jwt_demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><!--    起步依赖--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.13</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--        redis的包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--        图片验证码--><dependency><groupId>com.oopsguy.kaptcha</groupId><artifactId>kaptcha-spring-boot-starter</artifactId><version>1.0.0-beta-2</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version></dependency><!--        mysql相关的包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.18</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><!--        jwt的依赖--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.0.0</version></dependency></dependencies></project>

总结

1.maven打包springboot项目,jar包;
2.windows安装java环境,以及运行jar包;
3.Linux安装java环境,以及运行jar包;
4.运行jar包template might not exist报错及解决;

相关文章:

Jwt(Json web token)——从Http协议到session+cookie到Token Jwt介绍 Jwt的应用:登陆验证的流程

目录 引出从Http协议到session&cookie到TokenHTTP协议session & cookiesessioncookie为什么需要session & cookie? JavaEE传统解决长连接方案问题&#xff1a;分布式不适用解决方案&#xff1a;令牌Token Jwt&#xff0c;Json web tokenjwt的结构Header加密算法Ba…...

Java使用 java.util.regex.Pattern 正则表达式校验参数值是否规范

场景&#xff1a; java中我们可以利用 Pattern 注解对某个入参进行规则校验&#xff0c;但有些特殊参数在接口入口处不方便校验&#xff0c;需要在代码中校验 一、使用 Pattern 注解校验 Pattern(regexp "^[a-zA-Z0-9]$", message "xxx号限输入字母、…...

HDFS基本操作命令

这里写目录标题 HDFS Shell CLI客户端说明常用命令hadoop fs -mkdir [-p] <path>hadoop fs -ls [-h] [-R] [<path>...]上传文件到指定目录下方法一:hadoop fs -put [-f] [-p] <localsrc>.....<dst>方法二&#xff1a;hadoop fs -moveFromLocal <loc…...

git 实操

首先有安装好的git,安装好后,会在任一目录下右键出现git bash和git gui两个选项 打开git bash,设置好全局变量,用户名和邮箱,设置方法为: git config -- global user.name "xxx" git config --global user.email "xxxxxx.com" 1.创建版本库 git init 命…...

Visual Studio Code Python 扩展中的包管理

排版&#xff1a;Alan Wang Python 凭借其简单的语法和强大的库&#xff0c;目前已成为最流行的编程语言之一&#xff0c;也是最适合那些刚接触编程的人们的语言。但是&#xff0c;随着项目复杂性和规模的增长&#xff0c;管理依赖项的复杂性也会增加。当新用户不断承接更成熟的…...

spring学习笔记九

数据源对象管理 1、加入pom坐标 <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><!-- https://mvnrepository.com/artifact/c3p0/c3p0 --><depe…...

java list stream 使用

1、实现List对象集合的简单去重&#xff08;distinct()&#xff09; ​ List<User> list list.stream().distinct().collect(Collectors.toList()); ​2、实现List集合的根据属性&#xff08;name&#xff09;去重 list list.stream().filter(o -> o.getName() ! …...

两个Ubuntu电脑用SSH远程连接

两个Ubuntu电脑用SSH远程连接 1.ssh客户端及服务端的安装&#xff1a; 打开终端后&#xff0c;只需要以下两个命令即可 sudo apt-get install openssh-clientsudo apt-get install openssh-server2.启动ssh服务&#xff0c;执行以下命令&#xff1a; sudo /etc/init.d/ssh …...

讲解 @ServletComponentScan注解

目录: 1、用法介绍2、实例讲解 1、介绍 在SpringBoot项目启动器中添加ServletComponentScan注解后&#xff0c;SpringBoot在启动时会扫描并注册所有带有WebServlet&#xff08;控制器&#xff09;、WebFilter&#xff08;过滤器&#xff09;、WebListener&#xff08;监听器&a…...

20款奔驰S350商务型加装原厂前排座椅通风系统,夏天必备的功能

通风座椅的主动通风功能可以迅速将座椅表面温度降至适宜程度&#xff0c;从而确保最佳座椅舒适性。该功能启用后&#xff0c;车内空气透过打孔皮饰座套被吸入座椅内部&#xff0c;持续时间为 8 分钟。然后&#xff0c;风扇会自动改变旋转方向&#xff0c;将更凉爽的环境空气从座…...

Rust vs Go:常用语法对比(十一)

题目来自 Rust Vs Go: Which Language Is Better For Developing High-Performance Applications?[1] 202. Sum of squares Calculate the sum of squares s of data, an array of floating point values. 计算平方和 package mainimport ( "math")func main() { da…...

Spring MVC拦截器和跨域请求

一、拦截器简介 SpringMVC的拦截器&#xff08;Interceptor&#xff09;也是AOP思想的一种实现方式。它与Servlet的过滤器&#xff08;Filter&#xff09;功能类似&#xff0c;主要用于拦截用户的请求并做相应的处理&#xff0c;通常应用在权限验证、记录请求信息的日志、判断用…...

C++初阶--C++入门

目录 前言C关键字命名空间命名空间的定义命名空间的使用加命名空间名称及作用域限定符使用using namespace 命名空间名称引入使用using将命名空间中的成员引入 C的输入与输出缺省参数全缺省半缺省参数 函数重载参数类型不同参数个数不同参数类型顺序不同 引用引用特性 常引用使…...

Matlab实现PID控制仿真(附上30个完整仿真源码+数据)

本文介绍了如何使用Matlab实现PID控制器的仿真。首先&#xff0c;我们将简要介绍PID控制器的原理和控制算法。然后&#xff0c;我们将使用Matlab编写一个简单的PID控制器&#xff0c;并使用仿真环境来验证其性能。最后&#xff0c;我们将通过调整PID控制器的参数来优化控制系统…...

微信小程序:文件下载

目录 第一步 请求资源 第二步 获取资源后写入到微信本地 获取资源 写入资源(wx.getFileSystemManager)writeFile 的api 第三步 读取资源(openDocument与saveImageToPhotosAlbum) 第一步 请求资源 下面是请求接口中的脚本内容 export let baseUrl http://192.168.78.112…...

QString和QByteArray的区别

QString和QByteArray的区别 本质格式转换QString字符串格式化打印长度 本质 QString是对QByteArray的再次封装 QString可以通过char*来构造&#xff0c;也可以通过QByteArray来构造 QByteArray就是char* QString是编码后的char* QString也是封装了字符串, 但是内部的编码为utf…...

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架&#xff0c;它允许开发人员使用Web技术&#xff08;如HTML、CSS和JavaScript&#xff09;构建桌面应用程序&#xff0c;这些应用程序可以在Windows、macOS和Linux等操作系统上运行。 Electron的核心是Chromium浏览器内核和Node.js…...

【Nodejs】Express模板使用

1.Express脚手架的安装 安装Express脚手架有两种方式&#xff1a; 使用express-generator安装 使用命令行进入项目目录&#xff0c;依次执行&#xff1a; cnpm i -g express-generator可通过express -h查看命令行的指令含义 express -hUsage: express [options] [dir] Optio…...

【iOS】App仿写--管理系统

文章目录 前言一、账号界面二、功能界面三、添加功能四、删除功能五、更改功能六、查找功能七、排序功能八、退出功能总结 前言 在日常生活中&#xff0c;如果用文字来记述与管理我们数据会十分麻烦&#xff0c;并且人工成本较高&#xff0c;这里笔者给出一种管理系统的模版&a…...

JS实现队列的数据结构

创建queue.ts /*** 队列*/ export default class Queue<T> {private items: object;private count: number;private header: number;constructor() {this.items {};this.count this.header 0;}/*** 入队列* param element* returns 当前队列的数量*/enqueue(element:…...

title: 用 LangChain 构建基于资料库的问答机器人(四):通过代理使用外部工具

上一篇教程我们介绍了 ReAct 系统&#xff0c;这是一个非常强大的行为模式&#xff0c;但它需要编写大量的示例来告诉 LLM 如何思考、行动&#xff0c;并且为了遵循这个模式&#xff0c;还需要编写代码来分析生成文字、调用函数、拼接 prompt 等&#xff0c;这些工作都是十分繁…...

使用 CSS 自定义属性

我们常见的网站日夜间模式的变化&#xff0c;其实用到了 css 自定义属性。 CSS 自定义属性&#xff08;也称为 CSS 变量&#xff09;是一种在 CSS 中预定义和使用的变量。它们提供了一种简洁和灵活的方式来通过多个 CSS 规则共享相同的值&#xff0c;使得样式更易于维护和修改。…...

Unity 性能优化一:性能标准、常用工具

性能标准 推荐耗时&#xff1a; 性能提现到玩家直观感受&#xff0c;就是帧率&#xff0c;为了达到要求的帧率&#xff0c;就要控制CPU的耗时&#xff0c;不同类型的游戏&#xff0c;对帧率要求不一样。下面是推荐耗时&#xff1a; 推荐内存&#xff1a; 避免游戏闪退的重点…...

【http长连接+池化】

参考&#xff1a; https://it.cha138.com/ios/show-49862.html http://blog.chinaunix.net/uid-16480950-id-103597.html https://www.cnblogs.com/kevin-yuan/p/13731552.html https://www.jianshu.com/p/17e9aacca438 一、http长连接和短连接 HTTP协议是无状态的协议&#…...

opencv-20 深入理解HSV 色彩空间(通过指定,标记颜色等来拓展ROI区域)

RGB 色彩空间是一种被广泛接受的色彩空间&#xff0c;但是该色彩空间过于抽象&#xff0c;我们不能够直接通过其值感知具体的色彩。 我们更习惯使用直观的方式来感知颜色&#xff0c;HSV 色彩空间提供了这样 的方式。 通过 HSV色彩空间&#xff0c;我们能够更加方便地通过色调、…...

python调用arcgis功能一例

python调用arcgis功能一例 执行方法&#xff1a; D:\data\python>python test_Select.pywindow11下环境变量设置 此电脑/属性/系统/高级系统设置/高级/环境变量/path path中添加全局目录&#xff1a;C:\Python27\ArcGIS10.4 test_Select.py脚本内容 # Name: Select_Examp…...

Spring MVC 是什么?

一、什么是 Spring MVC&#xff1f; 官方对于 Spring MVC 的描述是这样的&#xff1a; Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web …...

Rust操作MySQL

查询 本部分是对 「Rust入门系列」Rust 中使用 MySQL[1]的学习与记录 经常使用的时间处理库&#xff1a; chrono 流式查询使用&#xff1a; query_iter 输出到Vec使用&#xff1a; query 映射到结构体使用&#xff1a; query_map 获取单条数据使用&#xff1a; query_first 命名…...

JAVA面试总结-Redis篇章(二)——缓存击穿

JAVA面试总结-Redis篇章&#xff08;二&#xff09; 缓存击穿解决方案一&#xff1a;互斥锁解决方案二&#xff1a;逻辑过期![在这里插入图片描述](https://img-blog.csdnimg.cn/176dfab3e26044a9a730fabea4314e8e.png) 缓存击穿 解决方案一&#xff1a;互斥锁 解决方案二&…...

Spring相关知识点

概述 分层的轻量级的全栈开源框架 展示层SprigMVC 持久层 Spring JDBCTemplate 业务层事务管理 注&#xff1a; 轻量级&#xff1a;API简单 全栈&#xff1a;各层都有相应解决方案 在Spring的体系结构中&#xff0c;由上而下&#xff0c;逐层依赖 Spring相当于是一个粘合剂&…...