• 前言

  秒杀的业务场景广泛存在于电商当中,即有一个倒计时的时间限制,当倒计时为0时,秒杀开始,秒杀之后持续很小的一段时间,而且秒杀的商品很少,因此会有大量的顾客进行购买,会产生很大的并发量,从而创造技术难点

  本章将编写一个不涉及并发操作的秒杀逻辑实现,包括商品页面,详情页面,以及订单页面。

  首先,当用户登录之后,跳转到商品页面,罗列了所有可以秒杀的商品。

  

 @Autowired
private GoodsService goodsService; @RequestMapping("/to_list")
public String list(Model model,MiaoshaUser user ){ List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("user",user);
model.addAttribute("goodsList",goodsList); return "goods_list";
}

  代码如上所示,这里之所以能取到user,是利用了分布式session的设计。

  • 商品页面代码 
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>商品列表</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!-- jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body> <div class="panel panel-default">
<div class="panel-heading">秒杀商品列表</div>
<table class="table" id="goodslist">
<tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr>
<tr th:each="goods,goodsStat : ${goodsList}">
<td th:text="${goods.goodsName}"></td>
<td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>
<td th:text="${goods.goodsPrice}"></td>
<td th:text="${goods.miaoshaPrice}"></td>
<td th:text="${goods.stockCount}"></td>
<td><a th:href="'/goods/to_detail/'+${goods.id}">详情</a></td>
</tr>
</table>
</div> </body>
</html>

从商品页面代码,点击详情的超链接可以到达去商品详情的控制器

@RequestMapping("/to_detail/{goodsId}")
public String detail(Model model, MiaoshaUser 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 remainSeconds=0; //设置秒杀状态
int miaoshaStatus=0; //判断
if(now<startAt){
//秒杀还没开始
miaoshaStatus=0;
remainSeconds= (int) ((startAt-now)/1000);
}else if(now>endAt){
//秒杀已经结束
miaoshaStatus=2;
remainSeconds=-1;
}else {
//秒杀正在进行
miaoshaStatus=1;
remainSeconds=0;
} model.addAttribute("miaoshaStatus",miaoshaStatus);
model.addAttribute("remainSeconds",remainSeconds); return "goods_detail";
}

该方法从页面传来商品id的值,然后从数据库中取出该商品的秒杀开始时间。结束时间等,判断秒杀的状态,0为未开始,1为正在进行,2为已经结束。然后返回商品详情。剩余时间、以及秒杀的状态,然后跳转到商品的详情页面,在详情页面中,详细列出该商品的信息。

  • 详情页代码

  

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>商品详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!-- jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body> <div class="panel panel-default">
<div class="panel-heading">秒杀商品详情</div>
<div class="panel-body">
<span th:if="${user eq null}"> 您还没有登录,请登陆后再操作<br/></span>
<span>没有收货地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" th:text="${goods.goodsName}"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="3"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>
</tr>
<tr>
<td>秒杀开始时间</td>
<td th:text="${#dates.format(goods.startDate, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td id="miaoshaTip">
<input type="hidden" id="remainSeconds" th:value="${remainSeconds}" />
<span th:if="${miaoshaStatus eq 0}">秒杀倒计时:<span id="countDown" th:text="${remainSeconds}"></span>秒</span>
<span th:if="${miaoshaStatus eq 1}">秒杀进行中</span>
<span th:if="${miaoshaStatus eq 2}">秒杀已结束</span>
</td>
<td>
<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>
</td>
</tr>
<tr>
<td>商品原价</td>
<td colspan="3" th:text="${goods.goodsPrice}"></td>
</tr>
<tr>
<td>秒杀价</td>
<td colspan="3" th:text="${goods.miaoshaPrice}"></td>
</tr>
<tr>
<td>库存数量</td>
<td colspan="3" th:text="${goods.stockCount}"></td>
</tr>
</table>
</div>
</body>
<script>
$(function(){
countDown();
}); function countDown(){
var remainSeconds = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0){//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
timeout = setTimeout(function(){
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
},1000);
}else if(remainSeconds == 0){//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout){
clearTimeout(timeout);
}
$("#miaoshaTip").html("秒杀进行中");
}else{//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀已经结束");
}
} </script>
</html>

详情页代码没有什么技术难点,主要有一个倒计时的功能,当确定秒杀还没有开始的时候,会显示一个倒计时,代码如下

function countDown(){
var remainSeconds = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0){//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
timeout = setTimeout(function(){
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
},1000);
}else if(remainSeconds == 0){//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout){
clearTimeout(timeout);
}
$("#miaoshaTip").html("秒杀进行中");
}else{//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀已经结束");
}

该方法通过判断剩余时间,如果剩余时间大于0,则,一直循环减1,如果进入秒杀则清理时间。

立即秒杀按钮是通过form表单提交的,会跳转到处理秒杀的控制器中,实际来说,秒杀有两个步骤,一个减少库存,一个写入订单。这两个步骤需要构成一个事务,如果其中一个出错,就需要回滚。这里可以使用事务注解@Transactional

来解决。

  • 秒杀控制器

@Controller
@RequestMapping("/miaosha")
public class MiaoshaController { @Autowired
private GoodsService goodsService; @Autowired
private OrderService orderService; @Autowired
private MiaoshaService miaoshaService; @RequestMapping("/do_miaosha")
public String list(Model model, MiaoshaUser user,
@RequestParam("goodsId") long goodsId){ model.addAttribute("user",user);
if(user==null){
//如果没有获取到user值,就跳转到登录页面去
return "login";
} //判断库存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
Integer stock= goods.getStockCount(); if(stock<=0){
model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
return "miaosha_fail";
} //判断是否已经秒杀到了
MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); if(order!=null){
model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
return "miaosha_fail";
} //进行秒杀逻辑
//减库存,下订单,写入秒杀订单
OrderInfo orderInfo=miaoshaService.miaosha(user, goods);
model.addAttribute("orderInfo",orderInfo);
model.addAttribute("goods",goods); return "order_detail";
}
}

首先获取user的值,获取不到,则跳转到登录页面,然后从商品数据库中获取库存,若大于0,则继续,否则跳转秒杀失败页面,然后判断是否秒杀商品已经在订单里了,如果在,也跳转到失败页面,因为不能重复秒杀。当前面的都没有问题,才开始进行秒杀操作,首先是减少库存,然后创建新订单。

  • 具体秒杀逻辑代码

  

@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
//减库存,下订单,写入秒杀订单 goodsService.reduceStock(goods); //抛出异常
// int i=1/0; return orderService.createOrder(user, goods); }
  • 减库存代码

  

 public void reduceStock(GoodsVo goods) {

        MiaoshaGoods g = new MiaoshaGoods();
g.setGoodsId(goods.getId());
goodsDao.reduceStock(g);
}
  • 下订单代码

  

 @Transactional
public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
OrderInfo orderInfo=new OrderInfo(); //设置订单详情
orderInfo.setCreateDate(new Date());
orderInfo.setDeliveryAddrId(0L);
orderInfo.setGoodsCount(1);
orderInfo.setGoodsId(goods.getId());
orderInfo.setGoodsName(goods.getGoodsName());
orderInfo.setGoodsPrice(goods.getGoodsPrice());
orderInfo.setGoodsPrice(goods.getGoodsPrice());
orderInfo.setStatus(0);
orderInfo.setOrderChannel(1);
orderInfo.setUserId(user.getId()); //保存订单
long orderId = orderDao.insert(orderInfo); //保存秒杀订单
MiaoshaOrder miaoshaOrder=new MiaoshaOrder();
miaoshaOrder.setGoodsId(goods.getId());
miaoshaOrder.setOrderId(orderId);
miaoshaOrder.setUserId(user.getId()); //抛出异常 //保存秒杀订单
orderDao.insertMiaoshaOrder(miaoshaOrder); return orderInfo;
}

至此,一个简单的秒杀功能已经实现,但这样的秒杀逻辑肯定不能抗住高并发,接下来将继续优化。

java初探(1)之秒杀的业务简单实现的更多相关文章

  1. [Java初探07]__关于面向对象的简单认识

    前言 类和对象,在我们学习Java语言的过程中,它们无时无刻不存在着,在我们还远未详细弄明白它们的意义之前,在我们不知不觉的下意识里,我们都会直接或间接的用到它们,这是因为Java是一门面向对象的语言 ...

  2. [java初探06]__排序算法的简单认识

    今天,准备填完昨天没填的坑,将排序算法方面的知识系统的学习一下,但是在简单的了解了一下后,有些不知如何组织学习了,因为排序算法的种类,实在是太多了,各有优略,各有适用的场景.有些不知所措,从何开始. ...

  3. [java初探总结篇]__java初探总结

    前言 终于,java初探系列的学习,要告一阶段了,java初探系列在我的计划中是从头学java中的第一个阶段,知识主要涉及java的基础知识,所以在笔记上实在花了不少的功夫.虽然是在第一阶段上面花费了 ...

  4. [java初探10]__关于数字处理类

    前言 在我们的日常开发过程中,我们会经常性的使用到数字类型的数据,同时,也会有众多的对数字处理的需求,针对这个方面的问题,在JAVA语言中.提供解决方法的类就是数字处理类 java中的数字处理类包括: ...

  5. JAVA使用POI读取EXCEL文件的简单model

    一.JAVA使用POI读取EXCEL文件的简单model 1.所需要的jar commons-codec-1.10.jarcommons-logging-1.2.jarjunit-4.12.jarlo ...

  6. JAVA Bean和XML之间的相互转换 - XStream简单入门

    JAVA Bean和XML之间的相互转换 - XStream简单入门 背景介绍 XStream的简介 注解简介 应用实例 背景介绍 我们在工作中经常 遇到文件解析为数据或者数据转化为xml文件的情况, ...

  7. 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

    第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造 MVC默认模板的视觉设计从MVC1到MVC3都没有改变,比较陈旧了:在MVC4中做了升级,好看些,在不同的分辨率下,也能工作得 ...

  8. java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可!

    java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可! 通过 云寻觅 javacallcsharp 生成器 自动生成java jni类库,  ...

  9. java生成RSA公私钥字符串,简单易懂

    java生成RSA公私钥字符串,简单易懂   解决方法: 1.下载bcprov-jdk16-140.jar包,参考:http://www.yayihouse.com/yayishuwu/chapter ...

随机推荐

  1. JAVAWEB开发下常见中文乱码问题解决

    JAVA环境下处理中文乱码问题一直是很多人困扰的问题,像URL传参乱码,写进数据库乱码,服务写中文文字图片乱码处理及导出PDF乱码. 1:安装中文支持 yum groupinstall "f ...

  2. Android 布局控件——滚动条视图,日期,时间

    今天学长讲了一些控件,比较强的那种控件. 刚开始讲了图片,但是图片我前面写过了就跳过. 滚动条牛牛们应该很熟悉,也常用哈. 这是垂直的滚动条视图哈 一起来用吧! <ScrollView andr ...

  3. CSS表单与数据表(上)

    表单在现代Web应用中占据着重要地位. 表单可以很简单,也可以非常复杂,要横跨几个页面. 除了从用户哪里获得数据,Web应用还需要以容易看懂的方式展示数据.表格是展示复杂数据的最佳方式. 1.设计数据 ...

  4. 8、Builder 建造者模式 组装复杂的实例 创造型模式

    1.什么是Builder模式 定义: 将一个复杂对象的构建与表示相分离,使得同样的构建过程可以创建不同的表示.大白话就是,你不需要知道这个类的内部是什么样的,只用把想使用的参数传进去就可以了,达到了解 ...

  5. Vue组件通信之父传子

    一般情况下,子组件中无法直接使用父组件的变量.借助子组件的props选项可以实现这一点. 这里我将一个vue实例作为一个父组件: const app = new Vue({ el:'#div1', d ...

  6. week4:周测错题

    4.如何在类外,给对象动态添加绑定方法 import types def qingtianzhu(obj,name): print("请我叫我一柱擎天,简称{},颜色是{}".fo ...

  7. MySQL数据库——查询数据

    增加数据: insert into "表名" values( '字段'',字段'); 或insert into '表名'( '字段'',字段')  values( '字段'',字段 ...

  8. Vue Elementui 表单必填项和非必填项label文字对齐的简单方式

    1. 不好的方式 很长时间以来都是用改写form-item样式来使得必填项和非必填项保证label对齐,这样需要改写系统样式,还要在相应的item上引用,代码量增多,示例如下(不推荐) <tem ...

  9. 题解 UVA10457

    题目大意:另s = 路径上的最大边权减最小边权,求u到v上的一条路径,使其s最小,输出这个s. 很容易想到枚举最小边然后跑最小瓶颈路. so,如何跑最小瓶颈路? 利用Kruskal,因为树上两点路径唯 ...

  10. Exception processing template "success": Exception parsing document: template="success",

    代码很简单 package com.kele.controller; import org.springframework.stereotype.Controller;import org.sprin ...