秒杀系统 秒杀静态化+订单详情静态化
秒杀静态化
改造商品详情页面的点击秒杀的业务逻辑,我们调用js方法实现ajax异步发送消息,如果秒杀成功,那么直接由客户端去跳转订单详情页面
window.location.href="order_detail.htm?orderId="+data.data.id;
function doMiaosha(){
//alert("秒杀!");
$.ajax({
url:"/miaosha/do_miaosha",
type:"POST",
data:{
goodsId:$("#goodsId").val()
},
success:function(data){
if(data.code==0){
//秒杀成功,跳转详情页面
window.location.href="order_detail.htm?orderId="+data.data.id;
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("请求有误!");
}
//token如果cookie里面有,会自己带过去
});
}
改造后台接收秒杀请求的doMiaosha方法接口,让其不在去跳转页面了,而是直接返回包装好的数据
原来我们是这样写的:
@RequestMapping("/do_miaosha")//传入user对象啊,不然怎么取user的值,${user.nickname}
public String toList(Model model,MiaoshaUser user,@RequestParam("goodsId") Long goodsId) {
model.addAttribute("user", user);
//如果用户为空,则返回至登录页面
if(user==null){
return "login";
}
GoodsVo goodsvo=goodsService.getGoodsVoByGoodsId(goodsId);
//判断商品库存,库存大于0,才进行操作,多线程下会出错
int stockcount=goodsvo.getStockCount();
if(stockcount<=0) {//失败 库存至临界值1的时候,此时刚好来了加入10个线程,那么库存就会-10
model.addAttribute("errorMessage", CodeMsg.MIAOSHA_OVER_ERROR);
return "miaosha_fail";
}
//判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品
MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdAndCoodsId(user.getId(),goodsId);
if(order!=null) {//重复下单
model.addAttribute("errorMessage", CodeMsg.REPEATE_MIAOSHA);
return "miaosha_fail";
}
//可以秒杀,原子操作:1.库存减1,2.下订单,3.写入秒杀订单--->是一个事务
OrderInfo orderinfo=miaoshaService.miaosha(user,goodsvo);
//如果秒杀成功,直接跳转到订单详情页上去。
model.addAttribute("orderinfo", orderinfo);
model.addAttribute("goods", goodsvo);
return "order_detail";//返回页面login
}
上面返回的是订单详情页面,但是现在我们通过json返回给我们的前台,秒杀成功则返回订单信息,不成功返回相应的数据信息,现在我们的代码如下:
/**
*
* 做了页面静态化的,直接返回订单的信息
*/
//POST请求
@RequestMapping(value="/do_miaosha",method=RequestMethod.POST)
@ResponseBody
public Result<OrderInfo> doMiaosha(Model model,MiaoshaUser user,@RequestParam(value="goodsId",defaultValue="0") long goodsId) {
model.addAttribute("user", user);
//如果用户为空,则返回至登录页面
if(user==null){
return Result.error(CodeMsg.SESSION_ERROR);
}
GoodsVo goodsvo=goodsService.getGoodsVoByGoodsId(goodsId);
//判断商品库存,库存大于0,才进行操作,多线程下会出错
int stockcount=goodsvo.getStockCount();
if(stockcount<=0) {//失败 库存至临界值1的时候,此时刚好来了加入10个线程,那么库存就会-10
//model.addAttribute("errorMessage", CodeMsg.MIAOSHA_OVER_ERROR);
return Result.error(CodeMsg.MIAOSHA_OVER_ERROR);
}
//判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品
MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdAndGoodsId(user.getId(),goodsId);
if(order!=null) {//重复下单
//model.addAttribute("errorMessage", CodeMsg.REPEATE_MIAOSHA);
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//可以秒杀,原子操作:1.库存减1,2.下订单,3.写入秒杀订单--->是一个事务
OrderInfo orderinfo=miaoshaService.miaosha(user,goodsvo);
//如果秒杀成功,直接跳转到订单详情页上去。
model.addAttribute("orderinfo", orderinfo);
model.addAttribute("goods", goodsvo);
return Result.success(orderinfo);
}
订单详情静态化
当秒杀成功之后,由客户端直接跳转至静态订单详情页面,与之前一样,初始化执行方法getOrderDetail,发起ajax请求获取数据来渲染我们的静态页面
$(function(){
getOrderDetail();
});
getOrderDetail方法:
function getOrderDetail() {
//取参数orderId
var orderId=getQueryString("orderId");
$.ajax({
url : "/order/detail",
type : "GET",
data : {
orderId :orderId
},
success : function(data) {
if (data.code == 0) {
render(data.data);
} else {
layer.msg(data.msg);
}
},
error : function() {
layer.msg("请求有误!");
}
});
}
render方法和getQueryString方法:
//渲染页面--------5-17
function render(detail){
//alert(detail.status);
var goods=detail.goodsVo;
var order=detail.order;
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src",goods.goodsImg);
$("#goodsPrice").text(order.goodsPrice);
$("#createDate").text(order.createDate);
//判断订单的状态orderStatus
var status="";
if(order.orderStatus==0){
$("#orderStatus").text("未支付");
}else if(order.orderStatus==1){
$("#orderStatus").text("待发货");
}else if(order.orderStatus==2){
$("#orderStatus").text("已发货");
}else if(order.orderStatus==3){
$("#orderStatus").text("待收货");
}
}
//获取请求路径里面的参数
function getQueryString(name){
var reg=new RegExp("(^|&)"+name+"=([^&]*)(&|$)");
var r=window.location.search.substr(1).match(reg);
if(r!=null){
return unescape(r[2]);
}
return null;
}
OrderDetailVo封装来专门给页面传值(json信息):
public class OrderDetailVo {
private GoodsVo goodsVo;
private OrderInfo order;
public GoodsVo getGoodsVo() {
return goodsVo;
}
public void setGoodsVo(GoodsVo goodsVo) {
this.goodsVo = goodsVo;
}
public OrderInfo getOrder() {
return order;
}
public void setOrder(OrderInfo order) {
this.order = order;
}
}
后台OrderController里面接收订单详情请求的接口代码:
@RequestMapping("/order")
@Controller
public class OrderController {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@RequestMapping("/detail")
@ResponseBody
public Result<OrderDetailVo> info(Model model,MiaoshaUser user,
@RequestParam("orderId") long orderId) {
if(user==null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
OrderInfo order=orderService.getOrderByOrderId(orderId);
if(order==null) {
return Result.error(CodeMsg.ORDER_NOT_EXIST);
}
//订单存在的情况
long goodsId=order.getGoodsId();
GoodsVo gVo=goodsService.getGoodsVoByGoodsId(goodsId);
OrderDetailVo oVo=new OrderDetailVo();
oVo.setGoodsVo(gVo);
oVo.setOrder(order);
return Result.success(oVo);//返回页面login
}
}
GoodsService 代码:
@Service
public class GoodsService {
public static final String COOKIE1_NAME_TOKEN="token";
@Autowired
GoodsDao goodsDao;
@Autowired
RedisService redisService;
public List<GoodsVo> getGoodsVoList() {
return goodsDao.getGoodsVoList();
}
public GoodsVo getGoodsVoByGoodsId(long goodsId) {
return goodsDao.getGoodsVoByGoodsId(goodsId);
}
public void reduceStock(GoodsVo goodsvo) {
MiaoshaGoods goods=new MiaoshaGoods();
goods.setGoodsId(goodsvo.getId());
goodsDao.reduceStock(goods);
}
}
解决超卖
超卖场景:
不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。
最简单的方法,更新数据库减库存的时候,进行库存限制条件,在reduceStock(GoodsVo goodsvo)这个方法里,sql要多加一个stock_count > 0即:
//stock_count>0的时候才去更新,数据库本身会有锁,那么就不会在数据库中同时多个线程更新一条记录,使用数据库特性来保证超卖的问题
@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
public void reduceStock(MiaoshaGoods goods);
也可以对读操作加上显式锁(select … for update)这样一来用户1在进行读操作时用户2就需要排队等待了,但是如果商品很热门并发量很高那么效率就会大大的下降。
订单详情页面order_detail.htm完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/><!--<meta charset="UTF-8" /> thymeleaf模板引擎默认是Template modes:HTML5解析的,所以解析比较严格。 -->
<title>订单详情</title>
<!-- thymeleaf引入静态资源的方式,@加大括弧 "/" 代表static路径-->
<!-- jquery -->
<!-- <script type="text/javascript" src="/js/jequery.min.js}"></script> -->
<script type="text/javascript" src="/jquery-validation/lib/jquery-1.11.1.js"></script>
<!-- bootstrap -->
<!-- <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"/>
-->
<link type="text/css" rel="stylesheet" href="/bootstrap/css/bootstrap.css"/>
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀订单详情</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" id="goodsName"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="2"><img id="goodsImg" width="80" height="60"></img></td>
</tr>
<tr>
<td>订单原价</td>
<td colspan="3" id="goodsPrice"></td>
</tr>
<tr>
<td>下单时间</td>
<td id="createDate" colspan="2"></td>
</tr>
<tr>
<td>订单状态</td>
<td id="orderStatus"><!-- 根据数值,做显示判断 -->
</td>
<td> <!-- 如果上面是未支付,那么下面会生成一个立即支付的按钮 -->
<button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
</td>
</tr>
<tr>
<td>收货人</td>
<td colspan="2">tom 15008484456</td>
</tr>
<tr>
<td>收货地址</td>
<td colspan="2">四川崇州市</td>
</tr>
</table>
</div>
</body>
<script type="text/javascript">
$(function(){
getOrderDetail();
});
function getOrderDetail() {
//取参数orderId
var orderId=getQueryString("orderId");
$.ajax({
url : "/order/detail",
type : "GET",
data : {
orderId :orderId
},
success : function(data) {
if (data.code == 0) {
render(data.data);
} else {
layer.msg(data.msg);
}
},
error : function() {
layer.msg("请求有误!");
}
//token如果cookie里面有,会自己带过去
});
}
//渲染页面--------5-17
function render(detail){
//alert(detail.status);
var goods=detail.goodsVo;
var order=detail.order;
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src",goods.goodsImg);
$("#goodsPrice").text(order.goodsPrice);
$("#createDate").text(order.createDate);
//判断订单的状态orderStatus
var status="";
if(order.orderStatus==0){
$("#orderStatus").text("未支付");
}else if(order.orderStatus==1){
$("#orderStatus").text("待发货");
}else if(order.orderStatus==2){
$("#orderStatus").text("已发货");
}else if(order.orderStatus==3){
$("#orderStatus").text("待收货");
}
}
//获取请求路径里面的参数
function getQueryString(name){
var reg=new RegExp("(^|&)"+name+"=([^&]*)(&|$)");
var r=window.location.search.substr(1).match(reg);
if(r!=null){
return unescape(r[2]);
}
return null;
}
</script>
</html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?