java初探(1)之秒杀的业务简单实现
前言
秒杀的业务场景广泛存在于电商当中,即有一个倒计时的时间限制,当倒计时为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)之秒杀的业务简单实现的更多相关文章
- [Java初探07]__关于面向对象的简单认识
前言 类和对象,在我们学习Java语言的过程中,它们无时无刻不存在着,在我们还远未详细弄明白它们的意义之前,在我们不知不觉的下意识里,我们都会直接或间接的用到它们,这是因为Java是一门面向对象的语言 ...
- [java初探06]__排序算法的简单认识
今天,准备填完昨天没填的坑,将排序算法方面的知识系统的学习一下,但是在简单的了解了一下后,有些不知如何组织学习了,因为排序算法的种类,实在是太多了,各有优略,各有适用的场景.有些不知所措,从何开始. ...
- [java初探总结篇]__java初探总结
前言 终于,java初探系列的学习,要告一阶段了,java初探系列在我的计划中是从头学java中的第一个阶段,知识主要涉及java的基础知识,所以在笔记上实在花了不少的功夫.虽然是在第一阶段上面花费了 ...
- [java初探10]__关于数字处理类
前言 在我们的日常开发过程中,我们会经常性的使用到数字类型的数据,同时,也会有众多的对数字处理的需求,针对这个方面的问题,在JAVA语言中.提供解决方法的类就是数字处理类 java中的数字处理类包括: ...
- JAVA使用POI读取EXCEL文件的简单model
一.JAVA使用POI读取EXCEL文件的简单model 1.所需要的jar commons-codec-1.10.jarcommons-logging-1.2.jarjunit-4.12.jarlo ...
- JAVA Bean和XML之间的相互转换 - XStream简单入门
JAVA Bean和XML之间的相互转换 - XStream简单入门 背景介绍 XStream的简介 注解简介 应用实例 背景介绍 我们在工作中经常 遇到文件解析为数据或者数据转化为xml文件的情况, ...
- 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造
第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造 MVC默认模板的视觉设计从MVC1到MVC3都没有改变,比较陈旧了:在MVC4中做了升级,好看些,在不同的分辨率下,也能工作得 ...
- java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可!
java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可! 通过 云寻觅 javacallcsharp 生成器 自动生成java jni类库, ...
- java生成RSA公私钥字符串,简单易懂
java生成RSA公私钥字符串,简单易懂 解决方法: 1.下载bcprov-jdk16-140.jar包,参考:http://www.yayihouse.com/yayishuwu/chapter ...
随机推荐
- 计算vtable的大小
在ClassFileParser::parseClassFile()函数中会计算vtable和itable所需要的大小,因为vtable和itable是内嵌在Klass中的,parseClassFil ...
- 吐血整理:二叉树、红黑树、B&B+树超齐全,快速搞定数据结构
前言 没有必要过度关注本文中二叉树的增删改导致的结构改变,规则操作什么的了解一下就好,看不下去就跳过,本文过多的XX树操作图片纯粹是为了作为规则记录,该文章主要目的是增强下个人对各种常用XX树的设计及 ...
- IPSec传输模式下的ESP报文的装包和拆包过程
IPSec协议定义 IPsec将IP数据包的内容在装包过程在网络层先加密再传输,即便中途被截获,由于缺乏解密数据包所必要的密钥,攻击者也无法获取里面的内容. IPsec 对数据进行加密的方式 加密模式 ...
- golang的fmt
前言 不做文字搬运工,多做思路整理 就是为了能速览标准库,只整理我自己看过的...... 注意!!!!!!!!!! 单词都是连着的,我是为了看着方便.理解方便才分开的 1.fmt 中文文档 [英文文档 ...
- 【WC2013】 糖果公园 - 树上莫队
问题描述 Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩.糖果公园的结构十分奇特,它由 n 个游览点构成, ...
- 深度优先搜索(dfs)与出题感想
在3月23号的广度优先搜索(bfs)博客里,我有提到写一篇深搜博客,今天来把这个坑填上. 第一部分:深度优先搜索(dfs) 以上来自百度百科. 简单来说,深度优先搜索算法就是——穷举法,即枚举所有情况 ...
- AMD 5700 XT显卡装ubuntu18.04.* 驱动的问题解决(全)
公司开发需要测试新的 AMD显卡,由于测试服务器上的显卡是英伟达的显卡所以换完后要安装相应的驱动.由于之前装机的同事装的ubuntu是18.04.5 恰巧18.04.5在amd官网上没有相匹配的驱动( ...
- 面试现场:裸面京东JavaT5第一轮电话面试
人物简介 清辞女朋友,94年资深女程序员一枚,15年非本专业毕业,有过1年测试经验,3年Java开发经验,之前有个1年大公司的工作经历,2年的车联网公司经历,每天打卡式工作,朝九晚七.工作之余没看过一 ...
- 0x01 基本算法-位运算 a^b
#include<bits/stdc++.h>using namespace std;int power(int a, int b, int p){ int ans = 1%p; ...
- 目录扫描、Nmap
一.基本定义 1.目录扫描: 扫描站点的目录,寻找敏感文件(目录名.探针文件.后台.robots.txt.备份文件等). 2.目录:站点结构,权限控制不严格. 3.探针文件:服务器配置信息,例:php ...