spring boot + mybatis 模拟银行系统余额查询、转账、存取钱功能实现 2.0(redis封装了对象)
我们在银行模拟系统1.0基础上对redis进行封装,银行模拟1.0系统项目代码地址以及详细讲解见网址:
银行模拟系统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 RedisTemplateredisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); RedisSerializer 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集成模拟银行转账、入账、出账、查询余额也就实现了。
至此,结束。