php攻城狮2019

百度云盘

 

一.秒杀业务分析
所谓秒杀,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。

秒杀商品通常有两种限制:时间限制,库存限制。

秒杀业务的运行流程主要可以分为以下几点:

商家提交秒杀商品申请,录入秒杀商品数据,主要有:商品标题,商品原价,秒杀价格,商品图片,介绍等信息
运营商审核秒杀申请
秒杀频道首页列出秒杀商品,点击秒杀商品图片可以跳转到秒杀商品详细页面
商品详细页面显示秒杀商品信息,点击立即抢购实现秒杀下单,下单时扣减库存,当库存为0或者不存在活动时间范围内时无法秒杀
秒杀下单成功,直接跳转到支付页面(扫码),支付成功,跳转到成功页面,填写收货、电话、收件人等信息,完成订单。
当用户秒杀下单5分钟内未支付,取消预订单,调用支付的关闭订单接口,恢复库存。
二.数据库设计
由上述业务流程可知,先要有秒杀商品表,并且不是提交到原来的普通订单表上,要设计一张秒杀订单表,故设计一个秒杀业务至少需要两张表。

秒杀商品表:

秒杀订单表:

三.秒杀实现思路
秒杀技术实现核心思想是运用缓存减少数据库瞬间的访问压力。读取商品详细信息时要运用缓存,当用户点击抢购时也要运用缓存,减少缓存中的库存数量,当库存数为0时或活动时间结束才同步到数据库中。产生的秒杀预订单也不会立刻写到数据库中,而是先写到缓存,当用户付款成功后再写入数据库。

四.实现关键步骤说明
返回秒杀商品List给前台秒杀频道(创建缓存)
//SeckillGoodsServiceImpl.java
//获取秒杀商品列表,缓存到Redis中
   @Autowired
    private RedisTemplate redisTemplate;    
    @Override
    public List<TbSeckillGoods> findList() {
        //获取秒杀商品列表
        List<TbSeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();
        if(seckillGoodsList==null || seckillGoodsList.size()==0){
            TbSeckillGoodsExample example=new TbSeckillGoodsExample();
            Criteria criteria = example.createCriteria();
            criteria.andStatusEqualTo("1");//审核通过
            criteria.andStockCountGreaterThan(0);//剩余库存大于0
            criteria.andStartTimeLessThanOrEqualTo(new Date());//开始时间小于等于当前时间
            criteria.andEndTimeGreaterThan(new Date());//结束时间大于当前时间
            seckillGoodsList= seckillGoodsMapper.selectByExample(example);        
            //将商品列表装入缓存
            System.out.println("将秒杀商品列表装入缓存");
            for(TbSeckillGoods seckillGoods:seckillGoodsList){
                redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getId(), seckillGoods);
            }            
        }
        return seckillGoodsList;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
秒杀详情页(读取缓存)
//SeckillGoodsServiceImpl.java
//根据秒杀商品id从缓存中获取秒杀商品
@Override
    public TbSeckillGoods findOneFromRedis(Long id) {
        return  (TbSeckillGoods)redisTemplate.boundHashOps("seckillGoods").get(id);
    }
1
2
3
4
5
6
秒杀还剩时间,前台利用商品的start_time和end_time控制。

秒杀下单(修改缓存)
商品详情页点击立即抢购实现秒杀下单,下单时候扣减库存。当库存为0或者超过活动时间范围内无法秒杀。
//SeckillOrderServiceImpl.java
//提交订单
@Autowired
    private RedisTemplate redisTemplate;
    
    @Autowired
    private IdWorker idWorker;
        
    @Override
    public void submitOrder(Long seckillId, String userId) {
        //从缓存中查询秒杀商品        
        TbSeckillGoods seckillGoods =(TbSeckillGoods)
        redisTemplate.boundHashOps("seckillGoods").get(seckillId);
        if(seckillGoods==null){
            throw new RuntimeException("商品不存在");
        }
        if(seckillGoods.getStockCount()<=0){
            throw new RuntimeException("商品已抢购一空");
        }    
        //扣减(redis)库存        
        seckillGoods.setStockCount(seckillGoods.getStockCount()-1);
        redisTemplate.boundHashOps("seckillGoods").put(seckillId, seckillGoods);//放回缓存
        if(seckillGoods.getStockCount()==0){//如果已经被秒光
            seckillGoodsMapper.updateByPrimaryKey(seckillGoods);//同步到数据库    
               redisTemplate.boundHashOps("seckillGoods").delete(seckillId);        
        }
        //保存(redis)订单
        long orderId = idWorker.nextId();
        TbSeckillOrder seckillOrder=new TbSeckillOrder();
        seckillOrder.setId(orderId);
        seckillOrder.setCreateTime(new Date());
        seckillOrder.setMoney(seckillGoods.getCostPrice());//秒杀价格
        seckillOrder.setSeckillId(seckillId);
        seckillOrder.setSellerId(seckillGoods.getSellerId());
        seckillOrder.setUserId(userId);//设置用户ID
        seckillOrder.setStatus("0");//状态
        redisTemplate.boundHashOps("seckillOrder").put(userId, seckillOrder);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
秒杀支付
用户成功下单后,跳转到支付页面,支付页面显示支付二维码。用户完成支付后,保存订单到数据库。
//SeckillOrderServiceImpl.java
//根据userId从缓存中获取用户秒杀订单
@Override
    public TbSeckillOrder  searchOrderFromRedisByUserId(String userId) {        
        return (TbSeckillOrder) redisTemplate.boundHashOps("seckillOrder").get(userId);
    }
1
2
3
4
5
6
//PayController.java
/**
 * 支付控制层
 *
 */
@RestController
@RequestMapping("/pay")
public class PayController {
    
    @Reference
    private  WeixinPayService weixinPayService;
    
    @Reference
    private SeckillOrderService seckillOrderService;    

    /**
     * 生成二维码
     * @return
     */
    @RequestMapping("/createNative")
    public Map createNative(){
        //获取当前用户        
        String userId=SecurityContextHolder.getContext().getAuthentication().getName();
        //到redis查询秒杀订单
        TbSeckillOrder seckillOrder = seckillOrderService.searchOrderFromRedisByUserId(userId);
        //判断秒杀订单存在
        if(seckillOrder!=null){
            long fen=  (long)(seckillOrder.getMoney().doubleValue()*100);//金额(分)
            return weixinPayService.createNative(seckillOrder.getId()+"",+fen+"");
        }else{
            return new HashMap();
        }        
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//SeckillOrderServiceImpl.java
//支付成功后保存订单
@Override
    public void saveOrderFromRedisToDb(String userId, Long orderId, String transactionId) {
        System.out.println("saveOrderFromRedisToDb:"+userId);
        //根据用户ID查询日志
        TbSeckillOrder seckillOrder = (TbSeckillOrder) redisTemplate.boundHashOps("seckillOrder").get(userId);
        if(seckillOrder==null){
            throw new RuntimeException("订单不存在");
        }
        //如果与传递过来的订单号不符
        if(seckillOrder.getId().longValue()!=orderId.longValue()){
            throw new RuntimeException("订单不相符");
        }        
        seckillOrder.setTransactionId(transactionId);//交易流水号
        seckillOrder.setPayTime(new Date());//支付时间
        seckillOrder.setStatus("1");//状态
        seckillOrderMapper.insert(seckillOrder);//保存到数据库
        redisTemplate.boundHashOps("seckillOrder").delete(userId);//从redis中清除
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//PyController.java
//添加查询订单
@RequestMapping("/queryPayStatus")
    public Result queryPayStatus(String out_trade_no){
        //获取当前用户        
        String userId=SecurityContextHolder.getContext().getAuthentication().getName();
        Result result=null;        
        int x=0;        
        while(true){
            //调用查询接口
            Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
            if(map==null){//出错            
                result=new  Result(false, "支付出错");
                break;
            }            
            if(map.get("trade_state").equals("SUCCESS")){//如果成功                
                result=new  Result(true, "支付成功");                
                seckillOrderService.saveOrderFromRedisToDb(userId, Long.valueOf(out_trade_no), map.get("transaction_id"));
                break;
            }            
            try {
                Thread.sleep(3000);//间隔三秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
            x++;//设置超时时间为5分钟
            if(x>100){
                result=new  Result(false, "二维码超时");
                break;
            }            
        }
        return result;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
订单超时处理
当用户下单后5分钟尚未付款应该释放订单,增加库存。
//SeckillOrderSeriveImpl.java
//删除缓存中的订单
    @Override
    public void deleteOrderFromRedis(String userId, Long orderId) {
    
        //1.查询出缓存中的订单
        
        TbSeckillOrder seckillOrder = searchOrderFromRedisByUserId(userId);
        if(seckillOrder!=null){

            //2.删除缓存中的订单 
            redisTemplate.boundHashOps("seckillOrder").delete(userId);
                        
            //3.库存回退
            TbSeckillGoods  seckillGoods = (TbSeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(seckillOrder.getSeckillId());
            if(seckillGoods!=null){ //如果不为空
                seckillGoods.setStockCount(seckillGoods.getStockCount()+1);
                redisTemplate.boundHashOps("seckillGoods").put(seckillOrder.getSeckillId(), seckillGoods);    
            }else{
                seckillGoods=new TbSeckillGoods();
                seckillGoods.setId(seckillOrder.getSeckillId());
                //属性要设置。。。。省略
                seckillGoods.setStockCount(1);//数量为1
                redisTemplate.boundHashOps("seckillGoods").put(seckillOrder.getSeckillId(), seckillGoods);
            }            
            
            System.out.println("订单取消:"+orderId);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
关闭支付宝/微信订单。

//WeixinPayServiceImpl.java
@Override
    public Map closePay(String out_trade_no) {
        Map param=new HashMap();
        param.put("appid", appid);//公众账号ID
        param.put("mch_id", partner);//商户号
        param.put("out_trade_no", out_trade_no);//订单号
        param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
        String url="https://api.mch.weixin.qq.com/pay/closeorder";
        try {
            String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
            HttpClient client=new HttpClient(url);
            client.setHttps(true);
            client.setXmlParam(xmlParam);
            client.post();
            String result = client.getContent();
            Map<String, String> map = WXPayUtil.xmlToMap(result);
            System.out.println(map);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//修改PayController.java
/**
     * 查询支付状态
     * @param out_trade_no
     * @return
     */
    @RequestMapping("/queryPayStatus")
    public Result queryPayStatus(String out_trade_no){
        //获取当前用户        
        String userId=SecurityContextHolder.getContext().getAuthentication().getName();
        Result result=null;        
        int x=0;        
        while(true){
            ........
            try {
                Thread.sleep(3000);//间隔三秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
            //不让循环无休止地运行定义变量,如果超过了这个值则退出循环,设置时间为1分钟
            x++;
            if(x>20){                
                result=new  Result(false, "二维码超时");    
                //1.调用微信的关闭订单接口
                Map<String,String> payresult = weixinPayService.closePay(out_trade_no);                
                if( !"SUCCESS".equals(payresult.get("result_code")) ){//如果返回结果是正常关闭
                    if("ORDERPAID".equals(payresult.get("err_code"))){
                        result=new Result(true, "支付成功");    
                        seckillOrderService.saveOrderFromRedisToDb(userId, Long.valueOf(out_trade_no), map.get("transaction_id"));
                    }                    
                }                
                if(result.isSuccess()==false){
                    System.out.println("超时,取消订单");
                    //2.调用删除
                    seckillOrderService.deleteOrderFromRedis(userId, Long.valueOf(out_trade_no));    
                }                
                break;
            }            
        }
        return result;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

发布了21 篇原创文章 · 获赞 43 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/JggAkk/article/details/103747147