银行模拟系统2.0 完整代码见地址:https://github.com/yeyuting-1314/tokenLogin-2.0.git
一、准备工作
我们要将redis进行对象缓存,我们就要实现redis缓存数据的序列化和反序列化,所谓序列化就是我们的value对象拿到对应内容存储到redis中的途中要将对象内容转化为字节流,传输到redis中,接着redis中要存储下来的时候需要将字节流转化为对象内容存储下来,便于我们进行存取查询操作,这里就需要实现redis 的反序列化了,首先我们就对redis进行相关的配置。
1. redis序列化,返回实体对象实现序列化接口,同时写入对应的序列编号(有时候也可以不加,但是有些时候不加的化会出现序列化编号的序列化对应不上的情况,进而出错,所以最好还是将编号也加上)这个位置不加容易出现异常,想了解可以看文章:
public class User implements Serializable {
private static final long serialVersionUID = 3529219554011221820L;
//其他代码
}
2. redis反序列化key和value对象,新建一个RedisConfig类,类中对反序列进行配置
/**
* @author yeyuting
* @create 2021/2/19
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
return template;
}
}
这里对反序列化进行较多研究,想深入了解可以阅读文章:
3. application.properties中对redis进行接口配置
#redis配置
spring.redis.port=6379
spring.redis.host=localhost
4. token的生成和解密重新设置生成方法。为啥之前的token生成方法不可以用了呢,这是因为我们需要一套token生成和解密方法,可以顺利实现token生成和解密,因此这里就给出了一套生成token和解密token的方法,便于后面将token进行解密进而拿到生成token的元素,例如用户名字等等。如下:
public String getToken(User user) {
String token = null;
try {
Date expiresAt = new Date(System.currentTimeMillis() + 24L * 60L * 3600L * 1000L);
token = JWT.create()
.withIssuer("auth0")
.withClaim("username", user.getUserName())
.withClaim("password", user.getPassword())
.withClaim("account" , user.getAccount())
.withExpiresAt(expiresAt)
// 使用了HMAC256加密算法。
// mysecret是用来加密数字签名的密钥。
.sign(Algorithm.HMAC256("mysecret"));
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return token;
}
public DecodedJWT deToken(final String token) {
DecodedJWT jwt = null;
try {
// 使用了HMAC256加密算法。
// mysecret是用来加密数字签名的密钥。
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("mysecret"))
.withIssuer("auth0")
.build(); //Reusable verifier instance
jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
//Invalid signature/claims
exception.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return jwt;
}
这样一来,准备工作就做好了,加下来集成redis存储对象。
二、正式工作
1. 首先用户登陆,访问loginCheck接口,如果数据库有该用户信息,则生成新的token并从数据库中拿到用户信息,以一个user对象作为value,用户名作为key存储到redis中,代码修改如下:
public Result loginCheck(User user , HttpServletResponse response){
User user1 = userMapper.selectByName(user.getUserName()) ;
if(user1 == null ){
return Results.failure("用户不存在") ;
}else if(!user1.getPassword().equals(user.getPassword())){
return Results.failure("密码输入错误!") ;
}
ValueOperations valueOperations = redisTemplate.opsForValue() ;
User newUser = (User)valueOperations.get(user1.getUserName()) ;
if(newUser != null){
jedisUtil.delString(user1.getUserName());
}
String token = tokenUtil.getToken(user1) ;
System.out.println("token:" + token);
user1.setToken(token);
valueOperations.set(user1.getUserName() , user1);
//jedisUtil.tokenToJedis(user1);
return Results.successWithData(user1);
}
postman访问情况后结果如下:
这时redis中成功存储了用户基本信息。
2.其他接口访问时都要先进入拦截器进行token认证,要记得将上面的token塞进各个接口头信息中,在这里我们就能看到token解密的魅力了,进行token认证的时候我们从头信息中只能拿到token信息,其余信息都拿不到,这时我们需要将此token和redis中的token对比,看是否能对应的上,这时token解密出来的用户名就派上了重要用场了。
/**
* @author yeyuting
* @create 2021/1/26
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
TokenUtil tokenUtil ;
@Autowired
RedisTemplate redisTemplate ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("token") ;
//Jedis jedis = new Jedis("localhost" , 6379) ;
if(StringUtils.isEmpty(token)){
response.sendRedirect("/sys/user/login");
return false ;
}
DecodedJWT jwt = tokenUtil.deToken(token) ;
String userName = jwt.getClaim("username").asString() ;
if((userName == null) ||(userName.trim().equals("")) ){
response.sendRedirect("/sys/user/login");
return false ;
}
System.out.println("token匹配成功!");
return true ;
}
}
(1)token认证通过后,紧接进行转账接口访问,记得要将上面的token塞进转账接口头信息中。
//转账
public String transferAccount(Double accountMoney , String targetAccount , HttpServletRequest request){
String token = request.getHeader("token") ;
DecodedJWT jwt = tokenUtil.deToken(token) ;
Double account = jwt.getClaim("account").asDouble() ;
String userName = jwt.getClaim("username").asString() ;
if(accountMoney > account){
return "余额不足" ;
}
User user1 = userMapper.selectByName(targetAccount) ;
if (user1.equals(null)){
return "对方账户不存在" ;
}
//转出账户余额更新
boolean result = userMapper.updateAccountOut(accountMoney , userName) ;
//转入账户余额更新
boolean result1 = userMapper.updateAccountIn(accountMoney , user1.getUserName()) ;
if ((result == false)||(result1 == false) ){
return "转账操作失败" ;
}
//转账记录生成------------
//String accountType = TransactionType.WITHDRAWMONEY ;
//出账记录生成
boolean insertReult = userMapper.accountOutInsert(userName ,account , accountMoney , targetAccount , TransactionType.WITHDRAWMONEY ) ;
//入账记录生成
//String accountType1 = TransactionType.SAVEMONEY ;
boolean insertReult1 = userMapper.accountInInsert(user1.getUserName() , user1.getAccount() , accountMoney , userName , TransactionType.SAVEMONEY ) ;
if((insertReult == false) || (insertReult1 == false)){
return "转账记录生成失败" ;
}
return "转账成功!" ;
}
(2)接下来是入账接口的实现进行修改:
//存钱
public String saveMoney(Double accountMoney , HttpServletRequest request){
String token = request.getHeader("token") ;
DecodedJWT jwt = tokenUtil.deToken(token) ;
Double account = jwt.getClaim("account").asDouble() ;
String userName = jwt.getClaim("username").asString() ;
//存入余额更新
boolean result = userMapper.updateAccountIn(accountMoney , userName) ;
if(result = false){
return "存入失败" ;
}
//存入记录生成
boolean insertResult = userMapper.accountInInsert(userName ,account , accountMoney , userName , TransactionType.SAVEMONEY) ;
if((insertResult == false)){
return "入账记录生成失败" ;
}
return "成功存入" + accountMoney + "元!" ;
}
(3)然后是出账接口实现修改:
//取钱
public String withdrawMoney(Double accountMoney , HttpServletRequest request){
String token = request.getHeader("token") ;
DecodedJWT jwt = tokenUtil.deToken(token) ;
Double account = jwt.getClaim("account").asDouble() ;
String userName = jwt.getClaim("username").asString() ;
if(accountMoney > account){
return "余额不足" ;
}
boolean result = userMapper.updateAccountOut(accountMoney , userName) ;
if(result = false){
return "取钱失败" ;
}
//出账记录生成
boolean insertResult = userMapper.accountOutInsert(userName ,account, accountMoney , userName , TransactionType.WITHDRAWMONEY) ;
if((insertResult == false)){
return "出账记录生成失败" ;
}
return "成功取出" + accountMoney + "元!" ;
}
(4)最后是查询接口的实现修改:
//查询余额
public Double selectByUserName1(String username , HttpServletRequest request) {
String token = request.getHeader("token") ;
DecodedJWT jwt = tokenUtil.deToken(token) ;
Double account = jwt.getClaim("account").asDouble() ;
return account ;
}
这样一来,redis集成模拟银行转账、入账、出账、查询余额也就实现了。
至此,结束。