本篇内容介绍了“如何自定义guava cache存储token”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
项目模块分析
项目主要分为七大模块:用户模块、商品模块、订单模块、分类模块、购物车模块、收货地址模块以及支付模块。
功能分析
核心模块
高复用的服务响应对应的设计思想和封装
用户模块
解决横向越权和纵向越权的问题,用户密码使用MD5明文加密,guava缓存实现信息的存储,用户功能主要包括:注册、登录、检测用户名是否有效,密码的重置,更新用户信息等等…
用户忘记密码以及密码提示问题和答案设计
当用户忘记密码需要重置密码的时候,需要输入注册时设置的密码提示问题和问题答案,比如:你的生日是什么时候? 1996-08-28,修改密码的时候需要校验密保问题和答案时候相一致,shopping-mall_v1.0的时候,我们采用guava cache来存储一个forgetToken,比设置token的有效期为30分钟(可根据项目场景选择),在重置密码的时候需要传递参数有:username,password,forgetToken三个参数,这时候需要判断在guava获取tokenCache里面的token和传进来的forgetToken是否一致,满足一致条件才能重置密码成功!
自定义guava cache存储token
public class TokenCache { //创建logback的logger private static Logger logger = LoggerFactory.getLogger(TokenCache.class); //生成token的前缀 public static final String TOKEN_PREFIX = "token_"; //声明一个静态的内存块,guava里面的本地缓存 public static LoadingCache<String,String> localCache = //构建本地缓存,调用链的方式,initialCapacity是缓存的初始化容量, // maxSize是缓存设置的最大内存容量,expireAfterAccess设置缓存的有效期为12个小时 CacheBuilder.newBuilder() .initialCapacity(1000) .maximumSize(10000) .expireAfterAccess(12, TimeUnit.HOURS) //build里面要实现一个匿名内部类 .build((new CacheLoader<String, String>() { //这个方法是默认的数据加载实现,当get的时候,如果key没有对应的值,就调用这个方法进行加载 @Override public String load(String key) throws Exception { //为什么要把return的null值写成字符串,因为到时候用null去.equal的时候,会报空指针异常 return "null"; } })); //添加本地缓存 public static void setKey(String key,String value){ localCache.put(key,value); } //得到本地缓存 public static String getValue(String key){ String value =null; try { value = localCache.get(key); if ("null".equals(value)){ return null; } return value; }catch (ExecutionException e){ logger.error("获取缓存getKey()方法错误",e); } return null; } }
商品模块
POJO、VO抽象模型的设计,高效的分页以及动态排序,使用FTP服务对接,富文本上传商品图片信息,商品模块比较重要的就是商品的搜索以及动态排序和商品分页,比如:根据关键字后台进行模糊查询,根据价格升序/降序排列,商品的分页主要集成 pagehelper 分页插件来完成,改分页插件实现数据分页相对高效,实用起来也比较简单,我们只需要使用PageHelper.startPage(pageNum,pageSize) 即可实现
订单模块
安全漏洞解决方案,考虑电商商品订单号的生成规则,订单状态的分析和订单枚举常量的设计,订单的超时关单处理(延时队列)
创建订单OrderVO类的封装,封装返回创建订单的所有信息,包括:订单信息,订单明细信息(List<OrderItemVO>),地址信息,创建订单时需要校验购物车商品的状态/数量,比如是否已下架,是否库存不足,校验成功才允许下单,下单成功之后需要减库存操作,需要遍历每一个订单Item,通过orderItem的productId获取到对应商品,执行product.getStock() – product.getQuantity();操作;
订单号的生成规则(时间戳+随机数的方式):
/** * orderNo生成方式 * * @return */ private long generateOrderNo() { //获取当前时间戳 long currentTime = System.currentTimeMillis(); //时间+[0,1000)之间的数即[0,999] return currentTime + new Random().nextInt(1000); }
定时关单,hour小时以内未付款的订单,进行自动关闭
相关实现逻辑:首先先获取前hour小时的订单,查询出这些订单里面为付款的订单,查询到这些订单之后,拿到即将关闭订里的商品和数量,取消订单时候库存需要加上商品数量(也就是库存还原),最后再设置订单状态。
@Override public void closeOrder(int hours) { //前hour小时的订单 Date closeDateTime = DateUtils.addHours(new Date(), -hours); List<Order> orderList = orderMapper.selectOrderStatusByCreateTime(Constant.OrderStatusEnum.NO_PAY.getCode(), dateToStr(closeDateTime)); for (Order order : orderList) { List<OrderItem> orderItemList = orderItemMapper.getByOrderNo(order.getOrderNo()); for (OrderItem orderItem : orderItemList) { //拿到即将要被关闭商品的和数量:一定要用主键where条件,防止锁表,同时必须支持MySQL的InnoDB引擎 Integer stock = productMapper.selectStockByProductId(orderItem.getProductId()); //考虑到已生成订单里的商品已被删除,这时候就不必更新了 if (stock == null) { continue; } Product product = new Product(); product.setId(orderItem.getProductId()); product.setStock(stock + orderItem.getQuantity()); productMapper.updateByPrimaryKeySelective(product); } orderMapper.closeOrderByOrderId(order.getId()); log.info("关闭订单orderNo:{}", order.getOrderNo()); } }
分类模块
采用递归算法,复杂对象的排重,无限层级的树状结构的设计
获取分类子节点(平级):当父及分类parentId传0的时候,查找的是跟节点的子分类
获取分类id及递归子节点分类id(返回本身以及它下面的子节点,假设0->10->100->1000,0的下一级子孩子节点为10,10的下一级节点为100,100的下一级节点为1000,业绩是返回:0(本身)->10->100->100),设计到递归查询算法,算法设计如下:
/** * 递归查询本节点的id以及孩子节点的id逻辑实现 * * @param categoryId * @return */ @Override public ServerResponse<List<Integer>> selectCategoryAndChildrenById(Integer categoryId) { Set<Category> categorySet = Sets.newHashSet(); this.findChildCategoryRecursive(categorySet, categoryId); //最后是返回categoryId List<Integer> categoryIdList = Lists.newArrayList(); if (categoryId != null) { for (Category categoryItem : categorySet) { categoryIdList.add(categoryItem.getId()); } } return ServerResponse.createBySuccess(categoryIdList); } /** * 递归查询算法,自己调自己,我们使用Set集合做返回,可以排重,这里要重写Category的HashCode和equals * 的两个方法为什么呢?当两个对象相同,即equals返回true,则他们的hashCode一定相同,但是当两个对象的 * hashCode相同,两个对象不一定相同,所以,得出结论当使用Set集合的时候,注意要重写equals和hashCode * 两个方法。 */ private Set<Category> findChildCategoryRecursive(Set<Category> categorySet, Integer categoryId) { Category category = categoryMapper.selectByPrimaryKey(categoryId); if (category != null) { categorySet.add(category); } //递归算法,查找子节点 List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId); for (Category categoryItem : categoryList) { //自己调用自己 findChildCategoryRecursive(categorySet, categoryItem.getId()); } return categorySet; } //mapper <select id="selectCategoryChildrenByParentId" parameterType="int" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from tb_category where parent_id = #{parentId} </select>
购物车模块
购物车模块核高复用的逻辑方法的封装,商品总价的计算复用和封装,使用BigDecimal类型解决商业运算丢失精度的问题,购物车的单选/反选,全选/全反选功能,一个购物车里面包括多个商品,添加购物车时需要校验购买的每个商品数量是否超过库存,超过库存这时候,购买的数量不能设为用户选择的数量,而是选择库存数量,计算价格的时候也只能按照现有库存数量乘以单价的方式计算总额。
收货地址模块
数据绑定和对象绑定,越权问题的升级个巩固,删除地址的时候为了防止横向越权我们不能只传一个shippingId,因此不能世界使用mybatis生成的deleteByPrimaryKey()的方法来删除,试想:当用户处于登陆状态的时候,如果只需要传一个shippingId就能实现删除的时候,那当传的不是个人的shippingId就会产生越权问题,也就是所谓的横向越权,为了防止横向越权我们需要自定义一个方法将收获地址和用户绑定,比如:自定义deleteByUserIdAndShippingId()方法来实现。(地址的更新也是一样的道理)
支付模块
支付宝SDK源码解析,分析了支付宝支付Demo的支付流程,将支付宝集成到项目中,包括:支付二维码的生成,扫码支付,支付的逻辑是:
第一步:查看订单的支付状态,如果order.getStatus()<=Constant.OrderStatusEnum.PAID.getCode()时,才满足支付条件
第二步:支付,采用支付宝当面付,需要生成支付二维码,其实这一步的关键就是创建扫描支付的请求biulder,通过builder来设置请求参数,首先会调用需下单的接口,判断预下单是否生成,如果预下单成功之后,需要将订单的状态修改为已支付,接着下单成功之后,会返回支付二维码,这是集成支付的比较关键的一步,我们需要做的就是保存这个支付二维码并上传到ftp服务器,最后返回一个二维码访问地址qrUrl给前端展示给用户,用户扫描对应的支付二维码即可支付订单,支付完成之后需要作支付宝回调处理,更新对应的订单状态为已支付,更新订单的支付方式。
支付宝官方支付场景过程说明:
1. 用户扫码
2.调用alipay.trade.precreate()请求生成二维码连接 -> 将二维码连接转二维码图片
3. 输入支付密码完成支付 -> 返回支付成功信息 -> 若支付成功,则返回异步信息(商户返回success)
4.调用alipay.trade.query()查询订单状态 -> 但会查询结果 -> 若返回支付成功(code=10000),则流程结束
5.若未在指定时间内未完成支付 -> 调用alipay.trade.cancel进行交易关闭
“如何自定义guava cache存储token”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
原创文章,作者:bd101bd101,如若转载,请注明出处:https://blog.ytso.com/223999.html