Java秒杀系统:web层详解

目录
  • 设计Restful接口
  • SpringMVC
    • 项目整合SpringMVC
  • 使用SpringMVC实现Restful接口
  • 逻辑交互
    • 身份认证
    • 计时面板
  • 总结

设计Restful接口

根据需求设计前端交互流程。

三个职位:

  • 产品:解读用户需求,搞出需求文档
  • 前端:不同平台的页面展示
  • 后端:存储、展示、处理数据

前端页面流程:

详情页流程逻辑:

标准系统时间从服务器获取。

Restful:一种优雅的URI表述方式、资源的状态和状态转移。

Restful规范:

  • GET 查询操作
  • POST 添加/修改操作(非幂等)
  • PUT 修改操作(幂等,没有太严格区分)
  • DELETE 删除操作

URL设计:

/模块/资源/{标示}/集合/...
/user/{uid}/friends -> 好友列表
/user/{uid}/followers -> 关注者列表

秒杀API的URL设计

GET /seckill/list 秒杀列表
GET /seckill/{id}/detail 详情页
GET /seckill/time/now 系统时间
POST /seckill/{id}/exposer 暴露秒杀
POST /seckill/{id}/{md5}/execution 执行秒杀

下一步就是如何实现这些URL接口。

SpringMVC

理论

适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。

SpringMVC的handlerControllerHttpRequestHandlerServlet等)有多种实现方式,例如继承Controller的,基于注解控制器方式的,HttpRequestHandler方式的。由于实现方式不一样,调用方式就不确定了。

看HandlerAdapter接口有三个方法:

// 判断该适配器是否支持这个HandlerMethod
boolean supports(Object handler);
// 用来执行控制器处理函数,获取ModelAndView 。就是根据该适配器调用规则执行handler方法。
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);

问流程如上图,用户访问一个请求,首先经过DispatcherServlet转发。利用HandlerMapping得到想要的HandlerExecutionChain(里面包含handler和一堆拦截器)。然后利用handler,得到HandlerAdapter,遍历所有注入的HandlerAdapter,依次使用supports方法寻找适合这个handler的适配器子类。最后通过这个获取的适配器子类运用handle方法调用控制器函数,返回ModelAndView。

注解映射技巧

  • 支持标准的URL
  • ?和*和**等字符,如/usr/*/creation会匹配/usr/AAA/creation和/usr/BBB/creation等。/usr/**/creation会匹配/usr/creation和/usr/AAA/BBB/creation等URL。带{xxx}占位符的URL。
  • 如/usr/{userid}匹配/usr/123、/usr/abc等URL.

请求方法细节处理

  • 请求参数绑定
  • 请求方式限制
  • 请求转发和重定向
  • 数据模型赋值
  • 返回json数据
  • cookie访问

返回json数据

cookie访问:

项目整合SpringMVC

web.xml下配置springmvc需要加载的配置文件:

<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true">
  <!--修改servlet版本为3.1-->
   <!--配置DispatcherServlet-->
  <servlet>
    <servlet-name>seckill-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--配置springmvc需要加载的配置文件
        spring-dao.xml spring-service.xml spring-web.xml-->
    <!--整合:mybatis -> spring -> springmvc-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>seckill-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

在resources文件夹下的spring文件夹添加spring-web.xml文件:

<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置springmvc-->
    <!--1. 开启springmvc注解模式-->
    <!-- 简化配置,
        自动注册handlermapping,handleradapter
        默认提供了一系列功能:数据绑定,数字和日期的format,xml和json的读写支持
     -->
    <mvc:annotation-driven>
      <!--servlet-mapping 映射路径:"/"-->
    <!--2. 静态资源默认servlet配置
        静态资源处理:js,gif,png..
        允许使用/做整体映射
    -->
    <mvc:default-servlet-handler>
     <!--3. jsp的显示viewResolver-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView">
        <property name="prefix" value="/WEB-INF/jsp">
        <property name="suffix" value=".jsp">
    </property></property></property></bean>
     <!--4. 扫描web相关的bean-->
    <context:component-scan base-package="cn.orzlinux.web">
</context:component-scan></mvc:default-servlet-handler></mvc:annotation-driven></beans>

使用SpringMVC实现Restful接口

新建文件:

首先是SeckillResult.java,这个保存controller的返回结果,做一个封装。

// 所有ajax请求返回类型,封装json结果
public class SeckillResult<t> {
    private boolean success; //是否执行成功
    private T data; // 携带数据
    private String error; // 错误信息
    // getter setter contructor
}

在Seckillcontroller.java中,实现了我们之前定义的几个URL:

GET /seckill/list 秒杀列表
GET /seckill/{id}/detail 详情页
GET /seckill/time/now 系统时间
POST /seckill/{id}/exposer 暴露秒杀
POST /seckill/{id}/{md5}/execution 执行秒杀

具体代码如下:

@Controller // @Service @Component放入spring容器
@RequestMapping("/seckill") // url:模块/资源/{id}/细分
public class SeckillController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private SecKillService secKillService;
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public String list(Model model) {
        // list.jsp + model = modelandview
        List<seckill> list = secKillService.getSecKillList();
        model.addAttribute("list",list);
        return "list";
    }
     @RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
    public String detail(@PathVariable("seckillId") Long seckillId, Model model) {
        if (seckillId == null) {
            // 0. 不存在就重定向到list
            // 1. 重定向访问服务器两次
            // 2. 重定向可以重定义到任意资源路径。
            // 3. 重定向会产生一个新的request,不能共享request域信息与请求参数
            return "redrict:/seckill/list";
         }
        SecKill secKill = secKillService.getById(seckillId);
        if (secKill == null) {
            // 0. 为了展示效果用forward
            // 1. 转发只访问服务器一次。
            // 2. 转发只能转发到自己的web应用内
            // 3. 转发相当于服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,
            //      两个文件(当前文件和目标文件)属于同一次请求,前后页 共用一个request,可以通
            //      过此来传递一些数据或者session信息
            return "forward:/seckill/list";
        }
        model.addAttribute("seckill",secKill);
        return "detail";
    }
     // ajax json
    @RequestMapping(value = "/{seckillId}/exposer",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF8"})
    @ResponseBody
    public SeckillResult<exposer> exposer(Long seckillId) {
        SeckillResult<exposer> result;
        try {
            Exposer exposer = secKillService.exportSecKillUrl(seckillId);
            result = new SeckillResult<exposer>(true,exposer);
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            result = new SeckillResult<>(false,e.getMessage());
        }
        return result;
    }
     @RequestMapping(value = "/{seckillId}/{md5}/execution",
        method = RequestMethod.POST,
        produces = {"application/json;charset=UTF8"})
    public SeckillResult<seckillexecution> execute(
            @PathVariable("seckillId") Long seckillId,
            // required = false表示cookie逻辑由我们程序处理,springmvc不要报错
            @CookieValue(value = "killPhone",required = false) Long userPhone,
            @PathVariable("md5") String md5) {
        if (userPhone == null) {
            return new SeckillResult<seckillexecution>(false, "未注册");
        }
        SeckillResult<seckillexecution> result;
        try {
            SeckillExecution execution = secKillService.executeSeckill(seckillId, userPhone, md5);
            result = new SeckillResult<seckillexecution>(true, execution);
            return result;
        } catch (SeckillCloseException e) { // 秒杀关闭
            SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.END);
            return new SeckillResult<seckillexecution>(false,execution);
        } catch (RepeatKillException e) { // 重复秒杀
            SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.REPEAT_KILL);
            return new SeckillResult<seckillexecution>(false,execution);
        } catch (Exception e) {
            // 不是重复秒杀或秒杀结束,就返回内部错误
            logger.error(e.getMessage(), e);
            SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.INNER_ERROR);
            return new SeckillResult<seckillexecution>(false,execution);
        }
     }
     @RequestMapping(value = "/time/now",method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<long> time() {
        Date now = new Date();
        return new SeckillResult<long>(true,now.getTime());
    }
}

页面

这里修改数据库为合适的时间来测试我们的代码。

点击后跳转到详情页。

详情页涉及到比较多的交互逻辑,如cookie,秒杀成功失败等等。放到逻辑交互一节来说。

运行时发现jackson版本出现问题,pom.xml修改为:

<dependency>
  <groupid>com.fasterxml.jackson.core</groupid>
  <artifactid>jackson-databind</artifactid>
  <version>2.10.2</version>
</dependency>

list.jsp代码为:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入jstl--%>
<%--标签通用头,写在一个具体文件,直接静态包含--%>
<%@include file="common/tag.jsp"%>

     <title>Bootstrap 模板</title>
    <%--静态包含:会合并过来放到这,和当前文件一起作为整个输出--%>
    <%@include file="common/head.jsp"%>

    <%--页面显示部分--%>
    <div class="container">
        <div class="panel panel-default">
            <div class="panel panel-heading text-center">
                <h1>秒杀列表</h1>
            </div>
            <div class="panel-body">
                <c:foreach var="sk" items="${list}">
                            </c:foreach><table class="table table-hover">
                    <thead>
                        <tr>
                            <th>名称</th>
                            <th>库存</th>
                            <th>开始时间</th>
                            <th>结束时间</th>
                            <th>创建时间</th>
                            <th>详情页</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                                <td>${sk.name}</td>
                                <td>${sk.number}</td>
                                <td>
                                    <fmt:formatdate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss">
                                </fmt:formatdate></td>
                                <td>
                                    <fmt:formatdate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss">
                                </fmt:formatdate></td>
                                <td>
                                    <fmt:formatdate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss">
                                </fmt:formatdate></td>
                                <td>
                                    <a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">
                                        link
                                    </a>
                                </td>
                            </tr>
                     </tbody>
                </table>
            </div>
        </div>
    </div>
 <!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>
 

逻辑交互

身份认证

cookie中没有手机号要弹窗,手机号不正确(11位数字)要提示错误:

选择提交之后要能够在cookie中看到:

目前为止detail.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

     <title>秒杀详情页</title>
    <%--静态包含:会合并过来放到这,和当前文件一起作为整个输出--%>
    <%@include file="common/head.jsp"%>
    <link href="https://cdn.bootcdn.net/ajax/libs/jquery-countdown/2.1.0/css/jquery.countdown.css" rel="stylesheet">

    <%--<input type="hidden" id="basePath" value="${basePath}">--%>
    <div class="container">
        <div class="panel panel-default text-center">
            <h1>
                <div class="panel-heading">${seckill.name}</div>
            </h1>
         </div>
        <div class="panel-body">
            <h2 class="text-danger">
                <!-- 显示time图标 -->
                <span class="glyphicon glyphicon-time"></span>
                <!-- 展示倒计时 -->
                <span class="glyphicon" id="seckillBox"></span>
            </h2>
        </div>
    </div>
    <!-- 登录弹出层,输入电话 bootstrap里面的-->
    <div id="killPhoneModal" class="modal fade bs-example-modal-lg">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title text-center">
                        <span class="glyphicon glyphicon-phone"></span>秒杀电话:
                    </h3>
                </div>
                <div class="modal-body">
                    <div class="row">
                        <div class="col-xs-8 col-xs-offset-2">
                            <input type="text" name="killphone" id="killphoneKey" placeholder="填手机号^O^" class="form-control">
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <span id="killphoneMessage" class="glyphicon"></span>
                    <button type="button" id="killPhoneBtn" class="btn btn-success">
                        <span class="glyphicon glyphicon-phone"></span> Submit
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <!-- jQuery cookie操作插件 -->
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
    <!-- jQery countDonw倒计时插件  -->
    <script src="https://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js"></script>
 <%--开始写交互逻辑--%>
<script src="/resources/script/seckill.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function () {
        seckill.detail.init({
            seckillId: ${seckill.seckillId},
            startTime: ${seckill.startTime.time}, // 转化为毫秒,方便比较
            endTime: ${seckill.endTime.time},
        });
    });
</script>

我们的逻辑主要写在另外的js文件中:

seckill.js

// 存放主要交互逻辑js
// javascript 模块化
var seckill={
    // 封装秒杀相关ajax的URL
    URL:{
     },
    // 验证手机号
    validatePhone: function (phone) {
        if(phone && phone.length==11 && !isNaN(phone)) {
            return true;
        } else {
            return false;
        }
    },
    // 详情页秒杀逻辑
    detail: {
        // 详情页初始化
        init: function (params) {
            // 手机验证和登录,计时交互
            // 规划交互流程
            // 在cookie中查找手机号
            var killPhone = $.cookie('killPhone');
            var startTime = params['startTime'];
            var endTime = params['endTime'];
            var seckillId = params['seckillId'];
            // 验证手机号
            if(!seckill.validatePhone(killPhone)) {
                // 绑定手机号,获取弹窗输入手机号的div id
                var killPhoneModal = $('#killPhoneModal');
                killPhoneModal.modal({
                    show: true, //显示弹出层
                    backdrop: 'static',//禁止位置关闭
                    keyboard: false, //关闭键盘事件
                });
                $('#killPhoneBtn').click(function () {
                    var inputPhone = $('#killphoneKey').val();
                    // 输入格式什么的ok了就刷新页面
                    if(seckill.validatePhone(inputPhone)) {
                        // 将电话写入cookie
                        $.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'});
                        window.location.reload();
                    } else {
                        // 更好的方式是把字符串写入字典再用
                        $('#killphoneMessage').hide().html('<label class="label label-danger">手机号格式错误</label>').show(500);
                    }
                });
            }
            // 已经登录
         }
    }
}

计时面板

在登录完成后,处理计时操作:

// 已经登录
// 计时交互
$.get(seckill.URL.now(),{},function (result) {
    if(result && result['success']) {
        var nowTime = result['data'];
        // 写到函数里处理
        seckill.countdown(seckillId,nowTime,startTime,endTime);
    } else {
        console.log('result: '+result);
    }
});

在countdown函数里,有三个判断,未开始、已经开始、结束。

URL:{
    now: function () {
        return '/seckill/time/now';
    }
},
 handleSeckill: function () {
    // 处理秒杀逻辑
},
 countdown: function (seckillId,nowTime,startTime,endTime) {
    var seckillBox = $('#seckillBox');
    if(nowTime>endTime) {
        seckillBox.html('秒杀结束!');
    } else  if(nowTime<starttime) {="" 秒杀未开始,计时="" var="" killtime="new" date(starttime="" +="" 1000);="" seckillbox.countdown(killtime,function="" (event)="" 控制时间格式="" format="event.strftime('秒杀开始倒计时:%D天" %h时="" %m分="" %s秒');="" seckillbox.html(format);="" 时间完成后回调事件="" }).on('finish.countdown',="" function="" ()="" 获取秒杀地址,控制显示逻辑,执行秒杀="" seckill.handleseckill();="" })="" }="" else="" 秒杀开始="" },="" ```="" 总体就是一个显示操作,用了jquery的countdown倒计时插件。="" <img="" src="https://gitee.com/hqinglau/img/raw/master/img/20211006194407.png" alt="image-20211006194407145" style="zoom:67%;">
 ### 秒杀交互

 秒杀之前:
 ![image-20211006202253376](https://img-blog.csdnimg.cn/img_convert/7609c513cb3b64f4e710d879e57c1651.png)
 详情页:
 <img src="https://gitee.com/hqinglau/img/raw/master/img/20211006201149.png" alt="image-20211006201149488" style="zoom:80%;">
 点击开始秒杀:
 <img src="https://gitee.com/hqinglau/img/raw/master/img/20211006202320.png" alt="image-20211006202320137" style="zoom:80%;">
 列表页刷新:
 ![image-20211006202306300](https://img-blog.csdnimg.cn/img_convert/272dac0d7f6d4a2910614551f4580aac.png)

 运行时发现controller忘了写`@ResponseBody`了,这里返回的不是jsp是json,需要加上。
 ```java
@ResponseBody
public SeckillResult<seckillexecution> execute(
        @PathVariable("seckillId") Long seckillId,
        // required = false表示cookie逻辑由我们程序处理,springmvc不要报错
        @CookieValue(value = "killPhone",required = false) Long userPhone,
        @PathVariable("md5") String md5)

在seckill.js中,补全秒杀逻辑:

// 封装秒杀相关ajax的URL
URL:{
    now: function () {
        return '/seckill/time/now';
    },
    exposer: function(seckillId) {
        return '/seckill/'+seckillId+'/exposer';
    },
     execution: function (seckillId,md5) {
        return '/seckill/'+seckillId+'/'+md5+'/execution';
    }
},

// id和显示计时的那个模块
handleSeckill: function (seckillId,node) {
    // 处理秒杀逻辑
    // 在计时的地方显示一个秒杀按钮
    node.hide()
        .html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
    // 获取秒杀地址
    $.post(seckill.URL.exposer(),{seckillId},function (result) {
        if(result && result['success']) {
            var exposer = result['data'];
            if(exposer['exposed']) {
                // 如果开启了秒杀
                // 获取秒杀地址
                var md5 = exposer['md5'];
                var killUrl = seckill.URL.execution(seckillId,md5);
                console.log("killurl: "+killUrl);
               // click永远绑定,one只绑定一次
                $('#killBtn').one('click',function () {
                    // 执行秒杀请求操作
                    // 先禁用按钮
                    $(this).addClass('disabled');
                    // 发送秒杀请求
                    $.post(killUrl,{},function (result) {
                        if(result) {
                             var killResult = result['data'];
                            var state = killResult['state'];
                            var stateInfo = killResult['stateInfo'];
                            // 显示秒杀结果
                            if(result['success']) {
                                node.html('<span class="label label-success">'+stateInfo+'</span>');
                            } else {
                                node.html('<span class="label label-danger">'+stateInfo+'</span>');
                            }

                        }
                        console.log(result);
                    })
                });
                node.show();
            } else {
                // 未开始秒杀,这里是因为本机显示时间和服务器时间不一致
                // 可能浏览器认为开始了,服务器其实还没开始
                var now = exposer['now'];
                var start = exposer['start'];
                var end = exposer['end'];
                // 重新进入倒计时逻辑
                seckill.countdown(seckillId,now,start,end);
            }
        } else {
            console.log('result='+result);
        }
    })
},

秒杀成功后再次进行秒杀则不成功:

输出:

在库存不够时也返回秒杀结束:

至此,功能方面已经实现了,后面还剩下优化部分。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 秒杀系统Web层设计的实现方法

    秒杀系统Web层设计的实现方法 一.Restful接口设计 使用资源+名词的方式来为url链接命名.例如: 访问详情页的链接可以是: seckill/{seckillId}/detail 二.SpringMVC配置 1.首先要在web.xml中配置中央控制器. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&q

  • 如何通过SpringBoot实现商城秒杀系统

    这篇文章主要介绍了如何通过SpringBoot实现商城秒杀系统,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 学习自:地址 1.主要流程 1.1数据库: 1.2 环境 window下:Zookeeper,Redis,rabbitmq-server.jdk1.8以上. 1.3 介绍 这里只做秒杀部分功能,其他功能不会涉及.项目运行后可访问秒杀商品页面 当用户没登陆,点击详情会跳转到登陆页面. 用户登陆后可以查看商品的详情并进行抢购. 注意,用户对

  • springboot集成redis实现简单秒杀系统

    本文实例为大家分享了springboot集成redis实现简单秒杀系统的具体代码,供大家参考,具体内容如下 项目是有地址的,我会放到文章的最后面 1. 直接service,我们会介绍两种秒杀模式 public interface GoodsService { /** * 通过lua脚本实现的秒杀 * @param skuCode 商品编码 * @param buyNum 购买数量 * @return 购买数量 */ Long flashSellByLuaScript(String skuCode

  • 如何设计一个秒杀系统

    什么是秒杀 秒杀场景一般会在电商网站举行一些活动或者节假日在12306网站上抢票时遇到.对于电商网站中一些稀缺或者特价商品,电商网站一般会在约定时间点对其进行限量销售,因为这些商品的特殊性,会吸引大量用户前来抢购,并且会在约定的时间点同时在秒杀页面进行抢购. 秒杀系统场景特点 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增. 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功. 秒杀业务流程比较简单,一般就是下订单减库存. 秒杀架构设计理念 限流: 鉴于只有少部分用

  • SpringBoot使用Redisson实现分布式锁(秒杀系统)

    前面讲完了Redis的分布式锁的实现,接下来讲Redisson的分布式锁的实现,一般提及到Redis的分布式锁我们更多的使用的是Redisson的分布式锁,Redis的官方也是建议我们这样去做的.Redisson点我可以直接跳转到Redisson的官方文档. 1.1.引入Maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter&l

  • 限时抢购秒杀系统架构分析与实战

    1 秒杀业务分析 正常电子商务流程 (1)查询商品:(2)创建订单:(3)扣减库存:(4)更新订单:(5)付款:(6)卖家发货 秒杀业务的特性 (1)低廉价格:(2)大幅推广:(3)瞬时售空:(4)一般是定时上架:(5)时间短.瞬时并发量高: 2 秒杀技术挑战 假设某网站秒杀活动只推出一件商品,预计会吸引1万人参加活动,也就说最大并发请求数是10000,秒杀系统需要面对的技术挑战有: 对现有网站业务造成冲击 秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原

  • Java秒杀系统:web层详解

    目录 设计Restful接口 SpringMVC 项目整合SpringMVC 使用SpringMVC实现Restful接口 逻辑交互 身份认证 计时面板 总结 设计Restful接口 根据需求设计前端交互流程. 三个职位: 产品:解读用户需求,搞出需求文档 前端:不同平台的页面展示 后端:存储.展示.处理数据 前端页面流程: 详情页流程逻辑: 标准系统时间从服务器获取. Restful:一种优雅的URI表述方式.资源的状态和状态转移. Restful规范: GET 查询操作 POST 添加/修改

  • web.xml详解_动力节点Java学院整理

    一.            Web.xml详解: (一)  web.xml加载过程(步骤) 首先简单说一下,web.xml的加载过程. 当我们去启动一个WEB项目时,容器包括(JBoss.Tomcat等)首先会读取项目web.xml配置文件里的配置,当这一步骤没有出错并且完成之后,项目才能正常地被启动起来.   启动WEB项目的时候,容器首先会去它的配置文件web.xml读取两个节点: <listener></listener>和<context-param><

  • Java中的反射机制详解

    Java中的反射机制详解 反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感觉对它没有一个较深入的了解,这次重新学习了一下,感觉还行吧! 一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高! 看概念很晕的,继续往下

  • Java大文件上传详解及实例代码

    Java大文件上传详解 前言: 上周遇到这样一个问题,客户上传高清视频(1G以上)的时候上传失败. 一开始以为是session过期或者文件大小受系统限制,导致的错误.查看了系统的配置文件没有看到文件大小限制,web.xml中seesiontimeout是30,我把它改成了120.但还是不行,有时候10分钟就崩了. 同事说,可能是客户这里服务器网络波动导致网络连接断开,我觉得有点道理.但是我在本地测试的时候发觉上传也失败,网络原因排除. 看了日志,错误为: java.lang.OutOfMemor

  • Java对象Serializable接口实现详解

    这篇文章主要介绍了Java对象Serializable接口实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 导读 最近这段时间一直在忙着编写Java业务代码,麻木地搬着Ctrl-C.Ctrl-V的砖,在不知道重复了多少次定义Java实体对象时"implements Serializable"的C/V大法后,脑海中突然冒出一个思维(A):问了自己一句"Java实体对象为什么一定要实现Serializable接口呢?&qu

  • 使用Go基于WebSocket构建千万级视频直播弹幕系统的代码详解

    (1)业务复杂度介绍 开门见山,假设一个直播间同时500W人在线,那么1秒钟1000条弹幕,那么弹幕系统的推送频率就是: 500W * 1000条/秒=50亿条/秒 ,想想B站2019跨年晚会那次弹幕系统得是多么的NB,况且一个大型网站不可能只有一个直播间! 使用Go做WebSocket开发无非就是三种情况: 使用Go原生自带的库,也就是 golang.org/x/net ,但是这个官方库真是出了奇Bug多 使用GitHub大佬 gorilla/websocket 库,可以结合到某些Web开发框

  • Java中文件管理系统FastDFS详解

    什么是FastDFS 很多以文件为载体的在线服务,如相册网站.视频网站等,都需要对文件进行管理,包括文件的存储.同步.访问(文件上传.文件下载)等,同时肯定会伴随着大容量存储和负载均衡的问题. 在日常的一些项目中,比如做用户的KYC认证等,也需要存储文件.图片.视频等.此时可以选择使用OSS云服务,也可以自己构建相对专业的文件管理系统. FastDFS是一个开源的轻量级分布式文件系统,用于解决大数据量存储和负载均衡等问题,并需要通过专有API进行访问.满足大容量文件存储问题,并保证高性能和高扩展

  • Java下SpringBoot创建定时任务详解

    序言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了. 三.基于注解设定多线程定时任务 一.静态:基于注解 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响. 1.创建定时器 使用SpringBoo

  • Java设计模式之原型模式详解

    一.前言 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式.在实际应用中,原型模式很少单独出现.经常与其他模式混用,他的原型类Prototype也常用抽象类来替代. 该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口 public class Prototype implements Cloneable { public

随机推荐