京东秒杀之秒杀实现

阅读: 评论:0

京东秒杀之秒杀实现

京东秒杀之秒杀实现

1 登录判断

用户在未登录状态下可以查看商品列别以及秒杀商品详情,但不可以在未登录状态进行秒杀商品的操作,当用户点击开始秒杀时,进行登陆验证

1.1 编写前端登录判断

<!DOCTYPE html>
<head><title>商品详情</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><script type="text/javascript" src="/js/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" /><!-- bootstrap --><script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script><script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script> <!-- jquery-validator --><script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script><script type="text/javascript" src="/layer/layer.js"></script><!-- layer --><script type="text/javascript" src="/js/md5.min.js"></script><!-- md5.js --><script type="text/javascript" src="/js/common.js"></script><!-- common.js --><script type="text/javascript" src="/js/socket.js"></script><!-- common.js -->
</head>
<body>
<div class="panel panel-default"><div class="panel-heading">秒杀商品详情</div><div class="panel-body"><div id="userTip" style="display: none"><span> 您还没有登录,请<a href="/login.html">登陆</a>后再操作<br/></span></div><span>没有收货地址的提示。。。</span></div><table class="table"><tr><td>商品名称</td><td colspan="3" id="goodName"></td></tr><tr><td>商品图片</td><td colspan="3"><img id="goodImg"  width="200" height="200" /></td></tr><tr><td>秒杀开始时间</td><td id="startDate"></td><td id="seckillTip"></td><td><img id="verifyCodeImg" width="80" height="32"  onclick="initVerifyCodeImg()" style="display: none"><input id="verifyCode" style="display: none"><button class="btn btn-primary btn-block" type="button" id="buyButton" onclick="doSeckill()">立即秒杀</button></td></tr><tr><td>商品原价</td><td colspan="3" id="goodPrice"></td></tr><tr><td>秒杀价</td><td colspan="3" id="seckillPrice"></td></tr><tr><td>库存数量</td><td colspan="3" id="stockCount"></td></tr></table>
</div>
<script type="text/javascript">var seckillId;$(function () {seckillId = getQueryString("seckillId");initGood();initUser();});function initGood(){$(function () {$.ajax({url: "localhost:9000/seckill/seckillGood/find?seckillId="+seckillId,type: "get",xhrFields: {withCredentials: true}, //启用cookiesuccess:function (data) {de==200){//填充表格中的数据renderGood(data.data);}else{layer.msg(data.msg)}}});});}function renderGood(good) {$("#goodName").dName);$("#goodImg").prop("src",dImg);$("#startDate").html(good.startDate);$("#goodPrice").dPrice);$("#stockCount").html(good.stockCount);$("#seckillPrice").html(good.seckillPrice);//调用时间renderDate(good.dDate);}//定义秒杀的三个阶段var timer;  //计时器//距离抢购开始还有多久var remainStartSeconds;//距离结束还有多久var remainEndSeconds;function renderDate(sDate,eDate) {var startTime = new Date(sDate);  // 2023-11-25 16:00var endTime = new Date(eDate);   // 2023-11-25 18:00var now = new Date();        // 2023-11-25 14:37remainStartSeconds=parseInt((Time()-Time())/1000);//秒remainEndSeconds=parseInt((Time()-Time())/1000);//秒timer=window.setInterval(showSeckillTip,1000);}function showSeckillTip() {remainStartSeconds--;remainEndSeconds--;if(remainStartSeconds>0){$("#seckillTip").html("距离本场秒杀开始还有"+remainStartSeconds+"秒");//禁用按钮$("#buyButton").prop("disabled",true);}else{if(remainEndSeconds>0){//秒杀中$("#seckillTip").html("秒杀进行中....");//禁用按钮$("#buyButton").prop("disabled",false);}else{$("#seckillTip").html("秒杀结束了");//禁用按钮$("#buyButton").prop("disabled",true);window.clearInterval(timer);//取消计时器}}}function initUser(){$(function () {$.ajax({url: "localhost:9000/member/token/getCurrent",type: "get",xhrFields: {withCredentials: true}, //启用cookiesuccess:function (data) {de==200){//填充表格中的数据renderUser(data.data);}else{layer.msg(data.msg)}}});});}var user;function renderUser(u){if(u){user=u;}else{//没有数据$("#userTip").show();}}</script>
</body>
</html>

1.2 编写后端登录判断方法

1 编写sevice接口及其实现类方法


sevice接口

    public User getUserByToken(String token);

实现类

    /*** 根据token查询用户* @param token* @return*/@Overridepublic User getUserByToken(String token) {(MemberServerKeyPrefix.USER_TOKEN, token, User.class);}

2 编写controller层方法

    /*** 获取当前用户* @param token 利用cookie中存储的token来判断用户* @return*/@GetMapping("/getCurrent")public Result<User> getCurrent(@CookieValue(value = CookieUtil.TOKEN_COOKIE_NAME, required = false) String token){User user = UserByToken(token);return Result.success(user);}

3 测试

2 后端登陆判断方法的优化

2.1 调整项目结构

1 添加依赖

2 将member-server下MemberServerKeyPrefix类移动至member-api下

3 将member-server下CookieUtil类移动至member-api下

4 编写getCookie方法

    /*** 获取cookie* @param request* @param tokenCookieName* @return*/public static String getCookie(HttpServletRequest request, String tokenCookieName) {Cookie[] cookies = Cookies();if (cookies != null && cookies.length > 0){for (Cookie cookie : cookies) {//找到cookieif (Name().equals(tokenCookieName)){Value();}}}return null;}

2.2 编写自定义SpringMVC参数解析器

public class UserMethodArgumentResolver implements HandlerMethodArgumentResolver {@Autowiredprivate MyRedisTemplate myRedisTemplate;/*** 判断参数类型是否为User* @param methodParameter* @return*/@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {ParameterType() == User.class &&ParameterAnnotation(RedisValue.class) != null;}/*** 自定义参数解析器* @param parameter* @param mavContainer* @param webRequest* @param binderFactory* @return* @throws Exception*/@Overridepublic Object resolveArgument(MethodParameter parameter,ModelAndViewContainer mavContainer,NativeWebRequest webRequest,WebDataBinderFactory binderFactory) throws Exception {//获取请求对象HttpServletRequest request = NativeRequest(HttpServletRequest.class);//获取cookieString token = Cookie(request, CookieUtil.TOKEN_COOKIE_NAME);if (StringUtils.isEmpty(token)){return null;}(MemberServerKeyPrefix.USER_TOKEN, token, User.class);}}

2.3 编写配置类

@Configuration
public class WebConfig implements WebMvcConfigurer {//注入参数解析器@Autowiredprivate UserMethodArgumentResolver userMethodArgumentResolver;/*** 添加参数解析器* @param resolvers*/@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userMethodArgumentResolver);}@Beanpublic UserMethodArgumentResolver userMethodArgumentResolver() {return new UserMethodArgumentResolver();}
}

2.4 编写自定义注解

@Target(ElementType.PARAMETER) //定义该注解应用于方法参数上
@Retention(RetentionPolicy.RUNTIME) //不仅保存在class中,JVM加载时也存在
public @interface RedisValue {}

2.5 编写登陆判断方法

    @RequestMapping("/getCurrent")public Result<User> getCurrent(@RedisValue User user){return Result.success(user);}

3 秒杀实现(同步)

实现流程:

    1. 判断用户是否登录
    1. 判断场次是否正常
    1. 根据秒杀的场次查出当前场次对应的秒杀商品
    1. 秒杀时间的问题 处于秒杀中才能抢购
    1. 判断用户是否已经参与过当前场次的抢购
    1. 判断库存是否足够
    1. 秒杀的原子性 【 做三件事 】
    • 7.1 商品的库存 -1 t_seckill_goods(生成抢购订单)
    • 7.2 创建商品的订单表 t_order_info(参与抢购的商品表)
    • 7.3 创建秒杀订单 t_seckill_order(防止用户重复抢购的表)

3.1 编写前端秒杀动作

    function doSeckill(){//判断用户是否登录if (!user){layer.msg("请先登录!");return;}$.ajax({url: "localhost:9000/seckill/order/doSeckill",type: "post",xhrFields: {withCredentials: true}, //启用cookiedata:{seckillId:seckillId},success:function (data) {de==200){window.location.href="order_detail.html?orderNo=" + data.data;}else{layer.msg(data.msg)}}});}

3.2 调整项目结构

1 添加依赖

2 添加参数解析器的配置类

将member-server下的参数解析器的配置类复制到seckill-server下

3.3 创建实体类

1 秒杀订单类

@Data
public class SeckillOrder implements Serializable {private Long id;private Long userId;private String orderNo;private Long seckillId;
}

3 订单商品类

@Data
public class OrderInfo implements Serializable {public static final Integer STATUS_CANCEL=2;  //手动取消订单public static final Integer STATUS_NO_PAY=0;//未付款public static final Integer STATUS_TIMEOUT=1;  //订单超时未支付public static final Integer STATUS_ACCOUNT_PAID=3;//已经付款过了private String orderNo;private Long userId;private Long goodId;private String goodImg;private Long deliveryAddrId;private String goodName;private Integer goodCount;private BigDecimal goodPrice;private BigDecimal seckillPrice;private Integer status=STATUS_NO_PAY;private Date createDate;private Date payDate;
}

3.4 编写属于订单的codeMsg

    public static  final SeckillCodeMsg SECKILL_OVER= new SeckillCodeMsg(500013,"秒杀结束了!");public static  final SeckillCodeMsg SECKILL_NOTBEGIN_OKOVER= new SeckillCodeMsg(500014,"不在秒杀时间段!!");public static  final SeckillCodeMsg REPEAT_SECKILL= new SeckillCodeMsg(500015,"您已经参与过秒杀了!");

3.5 创建雪花算法处理类

public class IdGenerateUtil {private long workerId;private long datacenterId;private long sequence = 0L;private long twepoch            = 1288834974657L;private long workerIdBits       = 5L;private long datacenterIdBits   = 5L;private long maxWorkerId        = -1L ^ (-1L << workerIdBits);private long maxDatacenterId    = -1L ^ (-1L << datacenterIdBits);private long sequenceBits       = 12L;private long workerIdShift      = sequenceBits;private long datacenterIdShift  = sequenceBits + workerIdBits;private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;private long sequenceMask       = -1L ^ (-1L << sequenceBits); //4095private long lastTimestamp      = -1L;private static class IdGenHolder {private static final IdGenerateUtil instance = new IdGenerateUtil();}public static IdGenerateUtil get() {return IdGenHolder.instance;}public IdGenerateUtil() {this(0L, 0L);}public IdGenerateUtil(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}public synchronized long nextId() {long timestamp = timeGen();if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果上次生成时间和当前时间相同,在同一毫秒内if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)| (workerId << workerIdShift) | sequence;}protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}protected long timeGen() {return System.currentTimeMillis();}}

3.5 创建mapper层

1 商品订单表(根据用户id和秒杀场次查询商品订单)

@Mapper
public interface SeckillOrderMapper {@Select("select * from t_seckill_order where user_id=#{userId} and seckill_id=#{seckillId}")public SeckillOrder findUserIdAndSeckillId(Long userId, Long seckillId) ;@Insert("INSERT INTO t_seckill_order (user_id,order_no,seckill_id) VALUES (#{userId},#{orderNo},#{seckillId})")public void createSeckillOrder(Long userId, Long seckillId, String orderNo);
}

2 订单详情表(根据用户id和秒杀场次查询商品的详细信息)

@Mapper
public interface OrderInfoMapper {/*** 添加订单到数据库中* @param orderInfo*/@Insert("INSERT INTO t_order_info (order_no,user_id,good_id,good_img,delivery_addr_id,good_name,good_count,good_price,seckill_price,status,create_date,pay_date) VALUES n" +"(#{orderNo},#{userId},#{goodId},#{goodImg},#{deliveryAddrId},#{goodName},#{goodCount},#{goodPrice},#{seckillPrice},#{status},#{createDate},null)")public void insert(OrderInfo orderInfo);
}

3.5 创建订单service业务逻辑接口及其实现类

1 商品订单表(根据用户id和秒杀场次查询商品订单)

public interface SeckillOrderService {public SeckillOrder findUserIdAndSeckillId(Long id, Long seckillId);public void createSeckillOrder(Long userId, Long seckillId, String orderNo);
}
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {@Autowiredprivate SeckillOrderMapper seckillOrderMapper;@Overridepublic SeckillOrder findUserIdAndSeckillId(Long id, Long seckillId) {return seckillOrderMapper.findUserIdAndSeckillId(id, seckillId);}@Overridepublic void createSeckillOrder(Long userId, Long seckillId, String orderNo) {ateSeckillOrder(userId, seckillId, orderNo);}
}

2 订单详情表(根据用户id和秒杀场次查询商品的详细信息)

public interface OrderInfoService {public String doSeckill(Long userId, Long seckillId);
}
@Service
public class OrderInfoServiceImpl implements OrderInfoService {@Autowiredprivate OrderInfoMapper orderInfoMapper;@Autowiredprivate SeckillGoodService seckillGoodService;@Autowiredprivate SeckillOrderService seckillOrderService;@Transactional //使用事务@Overridepublic String doSeckill(Long userId, Long seckillId) {//        7. 秒杀的原子性 【 需要做三件事 】//        7.1  商品的库存  -1    t_seckill_goodsseckillGoodService.decrStock(seckillId);//        7.2 创建商品的订单表  t_order_infoString orderNo = ateOrderInfo(userId, seckillId);//        7.3  创建秒杀订单  t_ateSeckillOrder(userId, seckillId, orderNo);return orderNo;}/*** 创建订单* @param userId* @param seckillId* @return*/private String createOrderInfo(Long userId, Long seckillId){OrderInfo orderInfo = new OrderInfo();//查询商品信息SeckillGoodVo vo = seckillGoodService.find(seckillId);//填写订单表orderInfo.setCreateDate(new Date());//送货地址  没有地址orderInfo.setDeliveryAddrId(null);//抢购,限制只卖一件orderInfo.setGoodCount(1);orderInfo.GoodId());orderInfo.GoodImg());orderInfo.GoodName());//原价orderInfo.GoodPrice());//秒杀价格orderInfo.SeckillPrice());//处理用户的idorderInfo.setUserId(userId);//用雪花算法加密订单编号orderInfo.().nextId()+"");//添加数据到  订单的表中  【配送  、售后 】orderInfoMapper.insert(orderInfo);//获取订单编号OrderNo();}
}

3.6 创建controller层

@RestController
@RequestMapping("/order")
public class OrderInfoController {@Autowiredprivate SeckillGoodService seckillGoodService;@Autowiredprivate SeckillOrderService seckillOrderService;@Autowiredprivate OrderInfoService orderInfoService;@RequestMapping("/doSeckill")public Result doSeckill(Long seckillId, @RedisValue User user){//      1. 判断用户是否登录if(user == null){(SeckillCodeMsg.LOGIN_TIMEOUT);}//        2. 判断场次是否正常[参数]if(seckillId == null || seckillId <= 0){(SeckillCodeMsg.OP_ERROR);}//        3. 根据秒杀的场次查出当前场次对应的秒杀商品SeckillGoodVo seckillGoodVo = seckillGoodService.find(seckillId);if(seckillGoodVo == null){  //  11   12   (SeckillCodeMsg.OP_ERROR);}//        4. 秒杀时间的问题  处于秒杀中才能抢购Date now = new Date();Time() < StartDate().getTime() ||Time() > EndDate().getTime()){//秒杀结束了(SeckillCodeMsg.SECKILL_NOTBEGIN_OKOVER);}//        5. 判断用户是否已经参与过当前场次的抢购SeckillOrder seckillOrder = seckillOrderService.Id(), seckillId);if(seckillOrder != null){  //  11   12   (SeckillCodeMsg.REPEAT_SECKILL);}//        6. 判断库存是否足够StockCount() <= 0){// 超卖(SeckillCodeMsg.SECKILL_OVER);}//        7. 秒杀的原子性 【 需要做三件事 】//        7.1  商品的库存  -1    t_seckill_goods//        7.2 创建商品的订单表  t_order_info//        7.3  创建秒杀订单  t_seckill_orderString orderNo=  orderInfoService.Id(), seckillId);return Result.success(orderNo);}}

3.7 测试

3.8 订单详情展示

1 编写前端页面

<!DOCTYPE html>
<html lang="en">
<head><title>商品详情</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><script type="text/javascript" src="/js/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" /><!-- bootstrap --><script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script><script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script> <!-- jquery-validator --><script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script><script type="text/javascript" src="/layer/layer.js"></script><!-- layer --><script type="text/javascript" src="/js/md5.min.js"></script><!-- md5.js --><script type="text/javascript" src="/js/common.js"></script><!-- common.js --><script type="text/javascript" src="/js/paySocket.js"></script><!-- common.js -->
</head>
<body>
<div class="panel panel-default"  style="height:100%;background-color:rgba(222,222,222,0.8)"><div class="panel-heading">秒杀订单详情</div><table class="table" id="goodslist"><tr><td>商品名称</td><td colspan="3" id="goodName"></td></tr><tr><td>商品图片</td><td colspan="2"><img id="goodImg"  width="200" height="200" /></td></tr><tr><td>订单价格</td><td colspan="2" id="seckillPrice"></td></tr><tr><td>下单时间</td><td colspan="2" id="createDate"></td></tr><tr><td>订单状态</td><td class="status_0" style="display: none">未支付<button class="btn btn-primary btn-block" type="button" id="payButton" onclick="pay()">立即支付</button></td><td class="status_1" style="display: none">已支付</td><td class="status_4" style="display: none">订单已取消(订单超时)</td></tr><tr><td>收货人</td><td colspan="2">liushao 18698789339</td></tr><tr><td>收货地址</td><td colspan="2">北京市海淀区成府路</td></tr></table>
</div>
<script type="text/javascript">var orderNo;$(function (){orderNo = getQueryString("orderNo");initOrder();});function initOrder(){$.ajax({url: "localhost:9000/seckill/order/find?orderNo=" + orderNo,type: "get",xhrFields: {withCredentials: true}, //启用cookiesuccess:function (data) {de==200){renderOrders(data.data)}else{layer.msg(data.msg)}}});}function renderOrders(order){$("#goodName").dName);$("#goodImg").prop("src", dImg);$("#goodPrice").dPrice);$("#createDate").ateDate);$(".status_" + order.status).show();}</script>
</body>
</html>

2 编写Mapper层方法

    @Select("SELECT * FROM t_order_info WHERE order_no=#{orderNo}")public OrderInfo find(String orderNo);

3 编写service业务逻辑接口及其实现类

接口

    public OrderInfo find(String orderNo);

实现类

    @Overridepublic OrderInfo find(String orderNo) {return orderInfoMapper.find(orderNo);}

4 编写controller层

    @RequestMapping("/find")public Result<OrderInfo> find(String orderNo, @RedisValue User user){//判断if(StringUtils.isEmpty(orderNo)){(SeckillCodeMsg.OP_ERROR);}if(user == null){(SeckillCodeMsg.LOGIN_TIMEOUT);}//根据订单编号查询订单OrderInfo orderInfo= orderInfoService.find(orderNo);//只能查看自己的订单if(orderInfo==null || !UserId().Id())){//订单编号有问题  或者用户和订单不匹配(SeckillCodeMsg.OP_ERROR);}return Result.success(orderInfo);}

5 测试

4 秒杀优化(异步)

本文发布于:2024-01-30 20:24:03,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170661744422602.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:杀之   京东   秒杀
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23