文章目录 1.显示秒杀状态 1.controller 修改GoodsController.java的toDetail方法,响应秒杀状态和秒杀剩余时间 2.前端 1.goodsDetail.html 图片下面添加一行秒杀开始时间 2.goodsDetail.html 添加计时器js代码 3.测试 1.秒杀进行中 2.修改db的秒杀开始时间为明天 3.出现秒杀倒计时 4.修改db的秒杀结束时间比目前要早 5.秒杀已结束 2.秒杀按钮 1.前端 1.goodsDetail.html 添加抢购按钮 2.goodsDetail.html 根据秒杀状态,控制按钮状态 2.测试 1.秒杀已结束,按钮不可用 2.秒杀进行中,按钮可用 3.秒杀未开始,按钮不可用 3.秒杀基本功能(不考虑高并发) 1.数据库表设计 2.MyBatis-Plus生成基础代码(以t_order表为例) 1.首先 ctrl + shift + c 复制基础包名(一定要是带点的) 2.右键表名选择 MybatisX-Generator 3.选择模块和基础包以及实体类名字 4.进行配置 5.点击生成,检查代码 1.整个目录概览 2.把实体类移动到pojo并检查 3.检查 OrderMapper.java 发现没有加@Mapper注解 4.查看启动类有@MapperScan所以不用加@Mapper了 5.检查OrderMapper.xml 6.检查application.yml是否自动扫描了Mapper.xml 7.检查OrderService.java 8.检查OrderServiceImpl.java 6.使用MyBatis-Plus生成基础代码的小结 7.以同样的方式生成t_seckill_order 3.Service层 1.OrderService.java 新增秒杀方法,返回订单 2.OrderServiceImpl.java 3.SeckillOrderService.java 根据普通订单和商品id插入秒杀订单 4.SeckillOrderServiceImpl.java 4.Controller层 1.SeckillController.java 完成基础版本的秒杀,简单考虑库存和复购问题 2.SeckillOrderService.java 新增方法,根据用户id和商品id查找记录 3.SeckillOrderServiceImpl.java 4.SeckillOrderMapper.java 5.SeckillOrderMapper.xml 5.前端 1.goodsDetail.html 修改点击抢购按钮的请求(区分多环境) 2.引入orderDetail.html 3.引入secKillFail.html 6.测试 1.正常秒杀 1.初始秒杀商品表(库存为10) 2.秒杀1号商品 3.秒杀成功 4.秒杀商品库存减1 5.普通订单新增一条记录 6.秒杀订单新增一条记录 2.当前用户再次购买 3.模拟库存不足的情况 1.将1号商品的库存修改为0 2.切换一个浏览器再次秒杀 3.成功跳转到库存不足的页面
1.显示秒杀状态
1.controller
修改GoodsController.java的toDetail方法,响应秒杀状态和秒杀剩余时间
@RequestMapping ( "/toDetail/{goodsId}" ) public String toDetail ( Model model, User user, @PathVariable Long goodsId) { if ( null == user) { return "login" ; } GoodsVo goodsVoByGoodsId = goodsService. findGoodsVoByGoodsId ( goodsId) ; model. addAttribute ( "goods" , goodsVoByGoodsId) ; long startAt = goodsVoByGoodsId. getStartDate ( ) . getTime ( ) ; long endAt = goodsVoByGoodsId. 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 ( "secKillStatus" , secKillStatus) ; model. addAttribute ( "remainSeconds" , remainSeconds) ; model. addAttribute ( "user" , user) ; return "goodsDetail" ; }
2.前端
1.goodsDetail.html 图片下面添加一行秒杀开始时间
< tr> < td> 秒杀开始时间</ td> < td id = " startTime" th: text= " ${#dates.format(goods.startDate,'yyyy-MM-dd HH:mm:ss')}" > </ td> < td id = " seckillTip" > < input type = " hidden" id = " remainSeconds" th: value= " ${remainSeconds}" /> < span th: if= " ${secKillStatus eq 0}" > 秒杀倒计时:< span id = " countDown" th: text= " ${remainSeconds}" > </ span> 秒</ span> < span th: if= " ${secKillStatus eq 1}" > 秒杀进行中</ span> < span th: if= " ${secKillStatus eq 2}" > 秒杀已结束</ span> </ td> </ tr>
2.goodsDetail.html 添加计时器js代码
< script> $ ( function ( ) { countDown ( ) ; } ) ; function countDown ( ) { var remainSeconds = $ ( "#remainSeconds" ) . val ( ) ; var timeout;
if ( remainSeconds > 0 ) { timeout = setTimeout ( function ( ) { $ ( "#countDown" ) . text ( remainSeconds - 1 ) ; $ ( "#remainSeconds" ) . val ( remainSeconds - 1 ) ; countDown ( ) ; } , 1000 ) ; } else if ( remainSeconds == 0 ) { if ( timeout) { clearTimeout ( timeout) ; } $ ( "#seckillTip" ) . html ( "秒杀进行中" ) ; } else { $ ( "#seckillTip" ) . html ( "秒杀已结束" ) ; } }
< / script>
3.测试
1.秒杀进行中
2.修改db的秒杀开始时间为明天
3.出现秒杀倒计时
4.修改db的秒杀结束时间比目前要早
5.秒杀已结束
2.秒杀按钮
1.前端
1.goodsDetail.html 添加抢购按钮
< td> < form id = " secKillForm" method = " post" action = " /seckill/doSeckill" > < input type = " hidden" id = " goodsId" name = " goodsId" th: value= " ${goods.id}" > < button class = " btn btn-primary btn-block" type = " submit" id = " buyButton" > 抢 购</ button> </ form> </ td>
2.goodsDetail.html 根据秒杀状态,控制按钮状态
2.测试
1.秒杀已结束,按钮不可用
2.秒杀进行中,按钮可用
3.秒杀未开始,按钮不可用
3.秒杀基本功能(不考虑高并发)
1.数据库表设计
1.普通订单表
use seckill;
DROP TABLE IF EXISTS ` t_order` ;
CREATE TABLE ` t_order`
( ` id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT , ` user_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , ` goods_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , ` delivery_addr_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , ` goods_name` VARCHAR ( 16 ) NOT NULL DEFAULT '' , ` goods_count` INT ( 11 ) NOT NULL DEFAULT '0' , ` goods_price` DECIMAL ( 10 , 2 ) NOT NULL DEFAULT '0.00' , ` order_channel` TINYINT ( 4 ) NOT NULL DEFAULT '0' COMMENT '订单渠道 1pc,2Android,
3ios' , ` status` TINYINT ( 4 ) NOT NULL DEFAULT '0' COMMENT '订单状态:0 新建未支付 1 已支付
2 已发货 3 已收货 4 已退款 5 已完成' , ` create_date` DATETIME DEFAULT NULL , ` pay_date` DATETIME DEFAULT NULL , PRIMARY KEY ( ` id` )
) ENGINE = INNODB AUTO_INCREMENT = 600 DEFAULT CHARSET = utf8mb4;
2.秒杀订单表
use seckill;
DROP TABLE IF EXISTS ` t_seckill_order` ;
CREATE TABLE ` t_seckill_order`
( ` id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT , ` user_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , ` order_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , ` goods_id` BIGINT ( 20 ) NOT NULL DEFAULT 0 , PRIMARY KEY ( ` id` ) , UNIQUE KEY ` seckill_uid_gid` ( ` user_id` , ` goods_id` ) USING BTREE COMMENT ' 用户 id,商品 id 的唯一索引,解决同一个用户多次抢购'
) ENGINE = INNODB AUTO_INCREMENT = 300 DEFAULT CHARSET = utf8mb4;
2.MyBatis-Plus生成基础代码(以t_order表为例)
1.首先 ctrl + shift + c 复制基础包名(一定要是带点的)
2.右键表名选择 MybatisX-Generator
3.选择模块和基础包以及实体类名字
4.进行配置
5.点击生成,检查代码
1.整个目录概览
2.把实体类移动到pojo并检查
3.检查 OrderMapper.java 发现没有加@Mapper注解
4.查看启动类有@MapperScan所以不用加@Mapper了
5.检查OrderMapper.xml
6.检查application.yml是否自动扫描了Mapper.xml
7.检查OrderService.java
8.检查OrderServiceImpl.java
6.使用MyBatis-Plus生成基础代码的小结
启动类配置MapperScan注解,扫描Mapper接口 application.yml配置扫描Mapper.xml 复制基础包名,要带点的 将Mapper.xml生成的最下面的删除即可
7.以同样的方式生成t_seckill_order
3.Service层
1.OrderService.java 新增秒杀方法,返回订单
package com. sxs. seckill. service ; import com. sxs. seckill. pojo. Order ;
import com. baomidou. mybatisplus. extension. service. IService ;
import com. sxs. seckill. pojo. User ;
import com. sxs. seckill. vo. GoodsVo ;
public interface OrderService extends IService < Order > { Order seckill ( User user, GoodsVo goodsVo) ;
}
2.OrderServiceImpl.java
package com. sxs. seckill. service. impl ; import com. baomidou. mybatisplus. core. conditions. query. QueryWrapper ;
import com. baomidou. mybatisplus. extension. service. impl. ServiceImpl ;
import com. sxs. seckill. mapper. SeckillGoodsMapper ;
import com. sxs. seckill. pojo. Order ;
import com. sxs. seckill. pojo. SeckillGoods ;
import com. sxs. seckill. pojo. User ;
import com. sxs. seckill. service. OrderService ;
import com. sxs. seckill. mapper. OrderMapper ;
import com. sxs. seckill. service. SeckillOrderService ;
import com. sxs. seckill. vo. GoodsVo ;
import org. springframework. stereotype. Service ; import javax. annotation. Resource ;
@Service
public class OrderServiceImpl extends ServiceImpl < OrderMapper , Order > implements OrderService { @Resource private SeckillGoodsMapper seckillGoodsMapper; @Resource private SeckillOrderService seckillOrderService; @Override public Order seckill ( User user, GoodsVo goodsVo) { SeckillGoods seckillGoods = seckillGoodsMapper. selectOne ( new QueryWrapper < SeckillGoods > ( ) . eq ( "goods_id" , goodsVo. getId ( ) ) ) ; seckillGoods. setStockCount ( seckillGoods. getStockCount ( ) - 1 ) ; seckillGoodsMapper. updateById ( seckillGoods) ; Order order = new Order ( ) ; order. setUserId ( user. getId ( ) ) ; order. setGoodsId ( goodsVo. getId ( ) ) ; order. setDeliveryAddrId ( 0L ) ; order. setGoodsName ( goodsVo. getGoodsName ( ) ) ; order. setGoodsCount ( 1 ) ; order. setGoodsPrice ( goodsVo. getSeckillPrice ( ) ) ; order. setOrderChannel ( 1 ) ; order. setStatus ( 0 ) ; order. setCreateDate ( null ) ; order. setPayDate ( null ) ; baseMapper. insert ( order) ; seckillOrderService. insertSeckillOrder ( order, goodsVo. getId ( ) ) ; return order; }
}
3.SeckillOrderService.java 根据普通订单和商品id插入秒杀订单
package com. sxs. seckill. service ; import com. sxs. seckill. pojo. Order ;
import com. sxs. seckill. pojo. SeckillOrder ;
import com. baomidou. mybatisplus. extension. service. IService ;
public interface SeckillOrderService extends IService < SeckillOrder > { void insertSeckillOrder ( Order order, Long goodsId) ;
}
4.SeckillOrderServiceImpl.java
package com. sxs. seckill. service. impl ; import com. baomidou. mybatisplus. extension. service. impl. ServiceImpl ;
import com. sxs. seckill. pojo. Order ;
import com. sxs. seckill. pojo. SeckillOrder ;
import com. sxs. seckill. service. SeckillOrderService ;
import com. sxs. seckill. mapper. SeckillOrderMapper ;
import org. springframework. stereotype. Service ;
@Service
public class SeckillOrderServiceImpl extends ServiceImpl < SeckillOrderMapper , SeckillOrder > implements SeckillOrderService { @Override public void insertSeckillOrder ( Order order, Long goodsId) { SeckillOrder seckillOrder = new SeckillOrder ( ) ; seckillOrder. setUserId ( order. getUserId ( ) ) ; seckillOrder. setOrderId ( order. getId ( ) ) ; seckillOrder. setGoodsId ( goodsId) ; baseMapper. insert ( seckillOrder) ; }
}
4.Controller层
1.SeckillController.java 完成基础版本的秒杀,简单考虑库存和复购问题
package com. sxs. seckill. controller ; import com. sxs. seckill. pojo. Order ;
import com. sxs. seckill. pojo. User ;
import com. sxs. seckill. service. GoodsService ;
import com. sxs. seckill. service. OrderService ;
import com. sxs. seckill. service. SeckillOrderService ;
import com. sxs. seckill. vo. GoodsVo ;
import com. sxs. seckill. vo. RespBeanEnum ;
import org. springframework. stereotype. Controller ;
import org. springframework. ui. Model ;
import org. springframework. web. bind. annotation. RequestMapping ; import javax. annotation. Resource ;
@Controller
@RequestMapping ( "/seckill" )
public class SeckillController { @Resource private GoodsService goodsService; @Resource private OrderService orderService; @Resource private SeckillOrderService seckillOrderService; @RequestMapping ( "/doSeckill" ) public String doSeckill ( Model model, User user, Long goodsId) { if ( user == null ) { return "login" ; } model. addAttribute ( "user" , user) ; GoodsVo goodsVoByGoodsId = goodsService. findGoodsVoByGoodsId ( goodsId) ; if ( goodsVoByGoodsId. getStockCount ( ) < 1 ) { model. addAttribute ( "errmsg" , RespBeanEnum . EMPTY_STOCK . getMessage ( ) ) ; return "secKillFail" ; } if ( seckillOrderService. findSeckillOrderByUserIdAndGoodsId ( user. getId ( ) , goodsId) != null ) { model. addAttribute ( "errmsg" , RespBeanEnum . REPEATE_ERROR . getMessage ( ) ) ; return "secKillFail" ; } Order seckill = orderService. seckill ( user, goodsVoByGoodsId) ; if ( seckill == null ) { model. addAttribute ( "errmsg" , RespBeanEnum . ERROR . getMessage ( ) ) ; return "secKillFail" ; } model. addAttribute ( "order" , seckill) ; model. addAttribute ( "goods" , goodsVoByGoodsId) ; return "orderDetail" ; }
}
2.SeckillOrderService.java 新增方法,根据用户id和商品id查找记录
SeckillOrder findSeckillOrderByUserIdAndGoodsId ( Long userId, Long goodsId) ;
3.SeckillOrderServiceImpl.java
@Override public SeckillOrder findSeckillOrderByUserIdAndGoodsId ( Long userId, Long goodsId) { return baseMapper. findSeckillOrderByUserIdAndGoodsId ( userId, goodsId) ; }
4.SeckillOrderMapper.java
package com. sxs. seckill. mapper ; import com. sxs. seckill. pojo. SeckillOrder ;
import com. baomidou. mybatisplus. core. mapper. BaseMapper ;
public interface SeckillOrderMapper extends BaseMapper < SeckillOrder > { SeckillOrder findSeckillOrderByUserIdAndGoodsId ( Long userId, Long goodsId) ;
}
5.SeckillOrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<! DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace = " com.sxs.seckill.mapper.SeckillOrderMapper" > < resultMap id = " BaseResultMap" type = " com.sxs.seckill.pojo.SeckillOrder" > < id property = " id" column = " id" jdbcType = " BIGINT" /> < result property = " userId" column = " user_id" jdbcType = " BIGINT" /> < result property = " orderId" column = " order_id" jdbcType = " BIGINT" /> < result property = " goodsId" column = " goods_id" jdbcType = " BIGINT" /> </ resultMap> < select id = " findSeckillOrderByUserIdAndGoodsId" resultType = " com.sxs.seckill.pojo.SeckillOrder" > SELECT*FROMt_seckill_orderWHEREuser_id = #{userId}ANDgoods_id = #{goodsId}</ select> </ mapper>
5.前端
1.goodsDetail.html 修改点击抢购按钮的请求(区分多环境)
2.引入orderDetail.html
< html lang = " en" xmlns: th= " http://www.thymeleaf.org" >
< head> < title> 订单详情</ title> < meta http-equiv = " Content-Type" content = " text/html; charset=UTF-8" /> < script type = " text/javascript" th: src= " @{/js/jquery.min.js}" > </ script> < link rel = " stylesheet" type = " text/css" th: href= " @{/bootstrap/css/bootstrap.min.css}" /> < script type = " text/javascript" th: src= " @{/bootstrap/js/bootstrap.js}" > </ script> < script type = " text/javascript" th: src= " @{/layer/layer.js}" > </ script> < script type = " text/javascript" th: src= " @{/js/common.js}" > </ script> < style> * { margin : 0; padding : 0; font-family : "Open Sans" , sans-serif; text-transform : uppercase; letter-spacing : 3px; font-size : 11px; } body { background : #c9302c; } .main-header { width : 100%; height : 100px; background : whitesmoke; display : block; } .navbar { display : inline-block; float : right; margin-right : 50px; margin-top : 30px; } .logo { display : inline-block; margin-top : 30px; margin-left : 30px; text-decoration : none; } .logo-lg { font-size : 20px; font-weight : lighter; color : #232324; } .logo-lg > b { font-size : 20px; font-weight : lighter; color : #232324; } .container { background : #FFFFFF; padding-right : 15px; padding-left : 15px; margin-right : auto; margin-left : auto; width : 750px; } </ style>
</ head>
< body>
< header id = " site-header" class = " main-header" > < a class = " logo" onclick = " toList ( ) " > < span class = " logo-lg" > < b> 商品抢购</ b> </ span> </ a> < nav class = " navbar navbar-static-top" > < a href = " #" class = " sidebar-toggle" data-toggle = " push-menu" role = " button" > < span class = " sr-only" > Toggle navigation</ span> < span class = " icon-bar" > </ span> < span class = " icon-bar" > </ span> < span class = " icon-bar" > </ span> </ a> < div class = " navbar-custom-menu" > < ul class = " nav navbar-nav" > < li class = " dropdown user user-menu" > < a href = " #" class = " dropdown-toggle" data-toggle = " dropdown" > < img class = " user-image" src = " /imgs/user.png" height = " 32" alt = " User
Image" > < span class = " hidden-xs" > </ span> </ a> < ul class = " dropdown-menu" > < li class = " user-header" > < img class = " img-circle" alt = " User Image" > < p> Hello ABC - Hello ABC< small> Hello ABC</ small> </ p> </ li> < li class = " user-body" > </ li> < li class = " user-footer" > < div class = " pull-middle" > < a onclick = " toList ( ) " class = " btn btn-lg btn-default btn-block" > 退出系统</ a> </ div> </ li> </ ul> </ li> </ ul> </ div> </ nav>
</ header>
< div class = " panel panel-default" > < div class = " panel-heading" style = " background : #c9302c; color : white" > 秒杀订单详情</ div> < div class = " container" > < table class = " table" id = " order" > < tr> < td> 名称</ td> < td id = " goodName" colspan = " 3" th: text= " ${goods.goodsName}" > </ td> </ tr> < tr> < td> 图片</ td> < td colspan = " 2" > < img id = " goodImg" width = " 200" th: src= " @{${goods.goodsImg}}" height = " 200" /> </ td> </ tr> < tr> < td> 订单价格</ td> < td colspan = " 2" id = " goodPrice" th: text= " ${order.goodsPrice}" > </ td> </ tr> < tr> < td> 下单时间</ td> < td id = " createDate" colspan = " 2" th: text= " ${#dates.format(order.createDate,'yyyy-MM-dd HH:mm:ss')}" > </ td> </ tr> < tr> < td> 订单状态</ td> < td id = " status" > < span th: if= " ${order.status eq 0}" > 未支付</ span> < span th: if= " ${order.status eq 1}" > 代发货</ span> < span th: if= " ${order.status eq 2}" > 已发货</ span> < span th: if= " ${order.status eq 3}" > 已收货</ span> < span th: if= " ${order.status eq 4}" > 已退款</ span> < span th: if= " ${order.status eq 5}" > 已完成</ span> </ td> < td> < button class = " btn btn-primary btn-block" type = " submit" id = " payButton" > 立即支付</ button> </ td> </ tr> < tr> < td> 收货人</ td> < td colspan = " 2" > XXX 13300000000</ td> </ tr> < tr> < td> 收货地址</ td> < td colspan = " 2" > 北京市幸福小区 6 单元 101 号</ td> </ tr> </ table> </ div>
</ div>
< script>
</ script>
</ body>
</ html>
3.引入secKillFail.html
< html lang = " en" xmlns: th= " http://www.thymeleaf.org" >
< head> < meta charset = " UTF-8" > < title> Title</ title> < style> * { margin : 0; padding : 0; font-family : "Open Sans" , sans-serif; text-transform : uppercase; letter-spacing : 3px; font-size : 11px; } </ style>
</ head>
< body>
< h1> 秒杀失败 : </ h1>
< p th: text= " ${errmsg}" > </ p>
</ body>
</ html>
6.测试
1.正常秒杀
1.初始秒杀商品表(库存为10)
2.秒杀1号商品
3.秒杀成功
4.秒杀商品库存减1
5.普通订单新增一条记录
6.秒杀订单新增一条记录
2.当前用户再次购买
成功跳转到限购页面
3.模拟库存不足的情况
1.将1号商品的库存修改为0
2.切换一个浏览器再次秒杀
3.成功跳转到库存不足的页面