Java秒杀系统及优化---(3)

三、实现秒杀功能

  • 数据库设计
  • 商品列表页
  • 商品详情页
  • 订单详情页

1、数据库设计(这里不贴SQL了,后面会给出代码)

四张表:商品表、秒杀商品表、订单表、秒杀订单表

1.1)为什么要扩展出一个秒杀商品表来?

直接在商品表中添加一个字段,是不是秒杀商品不就行了?乍一听,好像是可以,但是你想过没有,我们今天搞一次秒杀,明天又搞一次大促销,后天搞一次9块9包邮,随着时间增加,我们的活动可能会越来越多,假如说每一次都要用一个字段来标识,时间长了以后,这个表会越来越难以维护,并且这样的商品表特别不稳定,经常修改,修改之后,代码都要改,这个极其麻烦。

所以我们单独拿出一张表,通过一个商品ID来关联到我们的商品表,也就是说,我们的商品表是相对来说比较稳定的,主要是包含商品的基本信息,名称啊,图片啊,价格啊......

这个秒杀价格,我们把它加到秒杀商品表,秒杀商品还包含什么?开始时间啊,结束时间啊,数量啊……,同理,还有秒杀订单表

1.2)订单表中为什么要冗余商品名称?

就是方便生成订单列表时,不需要再去关联商品表了,直接把商品名称拉过来显示。

1.3)注意:实际项目中的价格存储,都是以整数形式存储,不使用小数存储,数据库中全是“分”

1.4)注意:数据库中的id,实际项目中很少设置成自增的,为什么啊?因为那样很容易让别人给遍历了,从1循环到最大值,把库中信息全部遍历,那么id正常来说如何设计呢?那应该用什么?分布式ID生成算法SnowFlake,分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种。这个以后再细说。

2、商品列表页

2.1)pojo层:创建对应的实体类:Goods、SecKillGoods、OrderInfo、SecKillOrder

2.2)dao层:创建GoodsDao,因为我们不仅想查出商品列表,还想直接把秒杀信息也查出来,但是商品表和秒杀商品表是在两个表里,所以再建立一个GoodsVo类,继承商品类,将秒杀商品的信息带过来,这样就把两个表合到一起了。

@Mapper
public interface GoodsDao {
    @Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")
    public List<GoodsVo> listGoodsVo();
}

2.3)service层:GoodsService

@Service
public class GoodsService {

    @Autowired
    private GoodsDao goodsDao;

    public List<GoodsVo> listGoodsVo() {
        return goodsDao.listGoodsVo();
    }
}

2.4)controller层:GoodsController

    @GetMapping("/to_list")
    public String list(Model model, SecKillUser user) {
        model.addAttribute("user", user);

        //查询商品列表
        List<GoodsVo> goodsList = goodsService.listGoodsVo();
        model.addAttribute("goodsList", goodsList);
        return "goods_list";
    }

2.5)测试:

完成!

3、商品详情页

3.1)pojo层:对应的实体类已创建

3.2)dao层:GoodsDao

    @Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id where g.id = #{goodsId}")
    public GoodsVo getGoodsVoByGoodsId(@Param("goodsId")long goodsId);

3.3)service层:GoodsService

    public GoodsVo getGoodsVoByGoodsId(long goodsId) {
        return goodsDao.getGoodsVoByGoodsId(goodsId);
    }

3.4)controller层:GoodsController

    @GetMapping("/to_detail/{goodsId}")
    public String detail(Model model,SecKillUser user,
                         @PathVariable("goodsId")long goodsId) {
        model.addAttribute("user", user);
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        model.addAttribute("goods", goods);

        long startAt = goods.getStartDate().getTime();
        long endAt = goods.getEndDate().getTime();
        long now = System.currentTimeMillis();

        int seckillStatus = 0;
        int remainSeconds = 0;
        if(now < startAt ) {//秒杀还没开始,倒计时
            seckillStatus = 0;
            remainSeconds = (int)((startAt - now )/1000);
        }else  if(now > endAt){//秒杀已经结束
            seckillStatus = 2;
            remainSeconds = -1;
        }else {//秒杀进行中
            seckillStatus = 1;
            remainSeconds = 0;
        }
        model.addAttribute("miaoshaStatus", seckillStatus);
        model.addAttribute("remainSeconds", remainSeconds);
        return "goods_detail";
    }

3.5)测试:

完成!可查看到商品具体信息了。

3.6)注意

秒杀按钮什么时候可以点击?

秒杀倒计时、秒杀结束,都不可以点;只有秒杀进行中可以点击。如果秒杀未开始,页面上要显示一个倒计时。

这个倒计时肯定是客户端来做,不应该取请求服务端,为什么呀?可想而知,我们的客户端非常多,大家都去请求服务端的话,访问量非常大,但是在客户端做会不会不精确啊,那肯定会不精确,没法跟服务器的时间保持完全一致,秒杀有时也是看运气。

4、订单详情页

上面,我们把详情页已经做好,现在我们实现秒杀功能。

做秒杀,其实很简单,就是做一个表单的提交:

<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
        		<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
        		<input type="hidden" name="goodsId" th:value="${goods.id}" />
        	</form>

提交时就传递了一个参数,商品id:name="goodsId",提交的路径:action="/miaosha/do_miaosha"

4.1)dao层:

GoodsDao

    @Update("update miaosha_goods set stock_count = stock_count -1 where goods_id = #{goodsId}")
    public int reduceStock(SecKillGoods g);

OrderDao

    @Select("select * from miaosha_order where user_id=#{userId} and goods_id=#{goodsId}")
    public SecKillOrder getSecKillOrderByUserIdGoodsId(@Param("userId")long userId, @Param("goodsId")long goodsId);


    @Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("
            + "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")
    @SelectKey(keyColumn="id", keyProperty="id", resultType=long.class, before=false, statement="select last_insert_id()")
    public long insert(OrderInfo orderInfo);


    @Insert("insert into miaosha_order (user_id, goods_id, order_id)"
            + "values(#{userId}, #{goodsId}, #{orderId})")
    public void insertSecKillOrder(SecKillOrder seckillOrder);

4.2)service层:

GoodsService

    public void reduceStock(GoodsVo goods) {
        SecKillGoods g = new SecKillGoods();
        g.setGoodsId(goods.getId());
        goodsDao.reduceStock(g);
    }

OrderService

public SecKillOrder getSecKillOrderByUserIdGoodsId(Long userId, Long goodsId) {
        return orderDao.getSecKillOrderByUserIdGoodsId(userId, goodsId);
    }

    @Transactional
    public OrderInfo createOrder(SecKillUser user, GoodsVo goods) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setUserId(user.getId());
        orderInfo.setGoodsId(goods.getId());
        orderInfo.setCreateDate(new Date());
        orderInfo.setDeliveryAddrId(0L);
        orderInfo.setGoodsName(goods.getGoodsName());
        orderInfo.setGoodsCount(1);
        orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
        //1pc, 2android, 3ios
        orderInfo.setOrderChannel(1);
        //订单状态,0新建未支付,1已支付,2已发货,3已收货,4,已退款,5已完成
        orderInfo.setStatus(0);

        long orderId = orderDao.insert(orderInfo);
        SecKillOrder secKillOrder = new SecKillOrder();
        secKillOrder.setUserId(user.getId());
        secKillOrder.setOrderId(orderId);
        secKillOrder.setGoodsId(goods.getId());
        orderDao.insertSecKillOrder(secKillOrder);
        return orderInfo;
    }

SecKillService

@Service
public class SecKillService {

    @Autowired
    private GoodsService goodsService;
    @Autowired
    private OrderService orderService;

    @Transactional
    public OrderInfo miaosha(SecKillUser user, GoodsVo goods) {
        //减库存、下订单、写入秒杀订单
        goodsService.reduceStock(goods);
        //创建订单信息,写入秒杀订单
        return orderService.createOrder(user, goods);
    }
}

通常情况下我们是在Service中使用自己的Dao,如果要使用非自己的Dao,此时并不是直接引用,而是引用的对用Dao的Service,这点要注意。为什么这么做呢???主要是为了更加清晰,也为了方便管理,Service用到自己的Dao则直接引用,用到别人的Dao,则引用对应的Serivice。

4.3)controller层:

    @PostMapping("/do_miaosha")
    public String list(Model model, SecKillUser user,
                       @RequestParam("goodsId")long goodsId) {
        model.addAttribute("user", user);
        if(user == null) {
            return "login";
        }
        //判断库存
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goods.getStockCount();
        if(stock <= 0) {
            model.addAttribute("errmsg", CodeMsg.SECKILL_OVER.getMsg());
            return "seckill_fail";
        }
        //判断是否已经秒杀到了
        SecKillOrder order = orderService.getSecKillOrderByUserIdGoodsId(user.getId(), goodsId);
        if(order != null) {
            model.addAttribute("errmsg", CodeMsg.REPEATE_SECKILL.getMsg());
            return "seckill_fail";
        }
        //减库存 下订单 写入秒杀订单
        OrderInfo orderInfo = secKillService.miaosha(user, goods);
        model.addAttribute("orderInfo", orderInfo);
        model.addAttribute("goods", goods);
        return "order_detail";
    }

4.4)CodeMsg 中添加

    public static CodeMsg SECKILL_OVER = new CodeMsg(500500, "商品已经秒杀完毕");
    public static CodeMsg REPEATE_SECKILL = new CodeMsg(500501, "不能重复秒杀");

4.5)测试:先修改一下时间,使其可以秒杀

秒杀成功后直接跳转到订单详情页面:

我们来看一下数据库:

秒杀商品表对应商品数量-1:

订单表新增一条记录:

秒杀订单表也新增一条记录:

同一个商品,再次点击秒杀时:

到此为止,秒杀的基本功能已经完成!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java实现秒杀系统@Controller @RequestMapping("seckill")//url:/模块/资源/{id}/细分 /seckill/list public class SeckillController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillService seckillService; @RequestMapping(value="/list",method = RequestMethod.GET) public String list(Model model){ //获取列表页 List list=seckillService.getSeckillList(); model.addAttribute("list",list); //list.jsp+model = ModelAndView return "list";//WEB-INF/jsp/"list".jsp } @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET) public String detail(@PathVariable("seckillId") Long seckillId, Model model){ if (seckillId == null){ return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null){ return "forward:/seckill/list"; } model.addAttribute("seckill",seckill); return "detail"; } //ajax json @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult exposer(@PathVariable("seckillId") Long seckillId){ SeckillResult result; try { Exposer exposer =seckillService.exportSeckillUrl(seckillId); result = new SeckillResult(true,exposer); } catch (Exception e) { logger.error(e.getMessage(),e); result = new SeckillResult(false,e.getMessage()); } return result; } @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public SeckillResult execute(@PathVariable("seckillId")Long seckillId,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值