• springboot+支付宝完成秒杀项目的初体验


    springboot+支付宝完成秒杀项目的初体验

    思考的问题:

    首先是秒杀的商品查询,考虑到是热点数据,所以写一个接口读取当日批次的秒杀商品到redis中(那么接下来对商品的操作都放入redis中)。
    当用户抢购商品时,考虑到的是是否在秒杀时间段内以及商品是抢完的问题。首先需要判断该商品是否在秒杀时间内,然后要查询该商品数量是否足够。当然这些还不够,还要有为了防止高并发的解决方案:
    • 对用户限流:对恶意请求通过ip设置访问次数,超过次数则抛出异常。
    • 利用消息队列的异步请求来削峰
    • 利用redis做缓存:提升读的效率,当然在秒杀的时候更改数量也可以在redis中完成,可以保证数据库的安全性。
    • 当然还有nginx方向代理,双击热备份,资源静态化,服务做集群,还有redis的主从集群

    最主要的还是数据库的读写问题,强一致问题


    当用户抢购时,进入并调用后端服务,这时候首先在redis中设置商品的键值对,然后读出redis中该商品的秒杀时间和数量,当满足条件时,更新redis中该商品的数量并在redis中生成订单。(这里用到了redis的锁,当这个商品的键没有时,则提交事物,当有时则不提交事物)

    这时候通过线程来判断该商品是否在一分钟之内付款,付款失败则将商品➕1,好让商品继续可以被秒杀。

    同时,为了防止该商品在事物过程中发生错误,导致该商品一直锁住,所以使用了try catch finally代码块,在finally中释放该锁,即是删除该键值对。

    核心代码如下:

    #######利用任务系统查询当日秒杀商品业务:
    public AppResult FindSkProduce() {
    //找到今日打折的商品放入redis中
    //条件 状态为1 数量大于0 在指定时间内
    //用hash(Map)存储 大键为cs1901_sk 小键为商品id 值为商品对象
    String s= DateUtils.getCurrentDate();
    String s1 = s + " 00:00:00";
    String s2 = s+ " 23:59:59";
    Date date1 = DateUtils.stringToDate(s1,DateUtils.DATE_TIME_FORMAT);
    Date date2 = DateUtils.stringToDate(s2,DateUtils.DATE_TIME_FORMAT);
    TbSeckillGoodsExample example=new TbSeckillGoodsExample();
    example.createCriteria().andEndTimeBetween(date1,date2).andStatusEqualTo("1").andNumGreaterThan(0);
    List glist=tbSeckillGoodsMapper.selectByExample(example);
    HashOperations hashOperations =redisTemplate.opsForHash();
    for (TbSeckillGoods tbSeckillGoods : glist) {
    System.out.println(tbSeckillGoods.getId());
    hashOperations.put("cs1901_sk",tbSeckillGoods.getId()+"",tbSeckillGoods);
    }
    //设置过期时间 过期时间为EndTime减去现在的时间(data2-nowData)
    Long expireTime=(date2.getTime()-new Date().getTime())/1000;
    System.out.println(expireTime);
    //设置过期时间用expire 注意是redisTemplate中的方法 参数(Key,Long 时间,时间单位(TimeUnit))
    redisTemplate.expire("cs1901_sk",expireTime,TimeUnit.SECONDS);

        return new AppResult(true,200,null,null);
    }
    
    秒杀业务:
    public AppResult CreateSkOrder(Long pid) {
        //创建订单
        //用redis的锁 锁住 该商品(在redis中查出该商品)
        //原理:redis 先设置唯一键 , 用redis 的事物和提交 ,再提交之前判断唯一键是否存在
        //watch(key)  若有该唯一键 则不提交事物,无则提交事物
        RedisLock redisLock=new RedisLock(redisTemplate,"lock"+pid);
        try {
            //事物
            //生成订单之前的判断:1 判断秒杀是否过期  2 判断商品数量是否足够
            //生成订单:1 生成唯一ID  将redis中的该商品数量减一  更新进redis
            //          2 生成订单 redis设置键位 大键为cs1901_sk_order 小键为订单id 值为订单
            //若抛出异常在finally中解锁 防止商品被锁死
            //新建线程 若一分钟未付款删除订单
            //用 thread
    
            //1 判断秒杀是否过期
            HashOperations hashOperations=redisTemplate.opsForHash();
            TbSeckillGoods tbSeckillGoods= (TbSeckillGoods) hashOperations.get("cs1901_sk",pid+"");
            if (redisTemplate.hasKey("cs1901_sk")==false){
                throw new AppExeption(200,"秒杀时间已过");
            }
            //2 判断商品数量是否大于0
            if (tbSeckillGoods.getNum()<=0){
                throw  new AppExeption(201,"商品已抢购完");
            }
            //1 更新数量
            tbSeckillGoods.setNum(tbSeckillGoods.getNum()-1);
            hashOperations.put("cs1901_sk",pid+"",tbSeckillGoods);
            //2 生成订单
            TbSeckillOrder tbSeckillOrder=new TbSeckillOrder();
            IDUtils idUtils=new IDUtils();
            Long oid=idUtils.nextId();
            tbSeckillOrder.setId(oid);
            tbSeckillOrder.setStatus("1");
            hashOperations.put("cs1901_sk_order",oid+"",tbSeckillOrder);
    
            //新建线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    TbSeckillOrder tbSeckillOrder1= (TbSeckillOrder) hashOperations.get("cs1901_sk_order",oid+"");
                    if (tbSeckillOrder1.getStatus().equals("1")){
                     //将商品数量加一
                        TbSeckillGoods tbSeckillGoods1= (TbSeckillGoods) hashOperations.get("cs1901_sk",pid+"");
                        tbSeckillGoods1.setNum(tbSeckillGoods.getNum()+1);
                        hashOperations.put("cs1901_sk",pid+"",tbSeckillGoods1);
                    }
                }
            }).start();
            return new AppResult(true,200,"已经抢到,请在一分钟内完成付款",oid+"");
        }catch (Exception ex){
            return new AppResult(false,201,"没有抢到",null);
        }finally {
            redisLock.unlock();
        }
    
    支付宝创建订单:
    @GetMapping("/pay/createsk/{orderid}")
    public void createsk(@PathVariable("orderid") Long orderid , HttpServletResponse response) throws Exception {
        //System.out.println(orderid+":"+total);
        //获得初始化的AlipayClient(生成支付宝客户端)
        AppResult result = payService.createSkPay(orderid,1L);
        if(result.isSuccess()==true){
            //把支付宝返回给我们的字符串   打印到客户端的浏览器
            response.getWriter().println(result.getData());
        }else{
            response.getWriter().println(result.getMessage());
        }
    }
    
    支付成功同步回调:
    @PostMapping("/asynonotice")
    @ResponseBody
    public String asynonotice(HttpServletRequest request) throws Exception {
        // 1: 获得支付宝返回的消息
        System.out.println("异步回调");
        String result = null;
        Map<String,String> params = new HashMap<String,String>();
        Map<String,String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(name, valueStr);
        }
        System.out.println(params);
        //2:  延签( )
        boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
                AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名
        if(signVerified==false){
            //return new AppResult(false,null,null,"fail");
            return "fail";
        }
        //3 判断 支付成功还是退款成功
        try {
            String trade_status = new String(request.getParameter("trade_status"));
            if(trade_status.equals("TRADE_FINISHED")){
                // 退款的话
                //payService.returnnotice(params);
                //...............
            }else if (trade_status.equals("TRADE_SUCCESS")){
                AppResult appResult = payService.PaySyncNotice(params);
                if(appResult.isSuccess()){
                    result = "success";
                }else{
                    result = "fail";
                }
            }
        } catch (Exception e) {
            result = "fail";
            e.printStackTrace();
        }
        System.out.println(result.toString());
        return "success";
    }
    
    支付成功异步回调:
    @PostMapping("/sk/syncnotice")
    @ResponseBody
    public String sksyncnotice(HttpServletRequest request) throws Exception {
    
        String result = null;
        // 1: 获得支付宝返回的消息
        Map<String,String> params = new HashMap<String,String>();
        Map<String,String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(name, valueStr);
        }
        //2:  验签( )
        boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
                AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名
        if(signVerified==false){
            //return new AppResult(false,null,null,"fail");
            return "fail";
        }
        //3 判断 支付成功还是退款成功
        try {
            String trade_status = new String(request.getParameter("trade_status"));
            if(trade_status.equals("TRADE_FINISHED")){
                // 退款的话
                //payService.returnnotice(params);
                //...............
            }else if (trade_status.equals("TRADE_SUCCESS")){
                AppResult appResult = payService.skpayNotice(params);
                if(appResult.isSuccess()){
                    result = "success";
                }else{
                    result = "fail";
                }
            }
        } catch (Exception e) {
            result = "fail";
            e.printStackTrace();
        }
        System.out.println(result.toString());
        return "success";
    }
  • 相关阅读:
    操作数组可以通过Array这个类来操作(不需要考虑数组的类型!!!)
    Servlet------>jsp自定义标签SimpleTag(jsp2.0以后的方法,1-5已经淘汰了)
    Servlet------>jsp自定义标签5(标签体内容改为大写)
    Servlet------>jsp自定义标签(JSPTAG接口)
    Servlet------>jsp自定义标签4(重复标签体)
    Servlet------>jsp自定义标签3(不显示余下jsp内容)
    模块学习
    正则表达式与re模块
    模块
    迭代器与生成器
  • 原文地址:https://www.cnblogs.com/tanyiming/p/11165515.html
Copyright © 2020-2023  润新知