温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何自定义guava cache存储token

发布时间:2021-12-16 09:20:43 来源:亿速云 阅读:181 作者:iii 栏目:大数据

本篇内容介绍了“如何自定义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

“如何自定义guava cache存储token”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI