基于SpringMVC+Spring+MyBatis实现秒杀系统【客户端交互】
前言
该篇主要实现客户端和服务的交互。在第一篇概况里我已经贴出了业务场景的交互图片。 客户端交互主要放在seckill.js里来实现。页面展现基于jsp+jstl来实现。
准备工作
1、配置web.xml。web.xml里配置springmvc前端控制器时需要把spring托管的3个xml全部加载。分别是spring-dao.xml、spring-service.xml、spring-web.xml。
<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"> <display-name>Archetype Created Web Application</display-name> <!--配置前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2、配置spring-web.xml
<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: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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--1、配置spring mvc --> <mvc:annotation-driven/> <!--2、静态资源默认配置--> <mvc:default-servlet-handler/> <!--3、配置视图--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!--4、扫描web相关controller--> <context:component-scan base-package="com.seckill.web"/> </beans>
秒杀接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | @Controller @RequestMapping ( "/seckill" ) public class SeckillController { @Autowired SeckillService seckillService; @RequestMapping ( "/list" ) public ModelAndView list(){ ModelAndView mav= new ModelAndView( "list" ); List<Seckill> list = seckillService.getSeckillList(); mav.addObject( "list" ,list); return mav; } /** * 返回值如果是ModelAndView时怎么控制重定向和转发呢 * **/ @RequestMapping (value= "/{seckillId}/detail/" ,method = RequestMethod.GET) public ModelAndView detail( @PathVariable ( "seckillId" )Long seckillId){ ModelAndView mav= new ModelAndView( "detail" ); Seckill seckill=seckillService.getById(seckillId); mav.addObject( "seckill" ,seckill); return mav; } //处理ajax请求返回json @RequestMapping (value= "/{seckillId}/exposer" ,method = RequestMethod.GET,produces = { "application/json;charset=UTF-8" }) @ResponseBody public SeckillResult<Exposer> exposer( @PathVariable ( "seckillId" )Long seckillId){ SeckillResult<Exposer> result= null ; try { Exposer exposer=seckillService.exposeSeckillUrl(seckillId); result= new SeckillResult<Exposer>( true ,exposer); } catch (Exception e){ result= new SeckillResult<Exposer>( false ,e.getMessage()); } return result; } @RequestMapping (value= "/{seckillId}/{md5}/execute" ,method = RequestMethod.POST,produces = { "application/json;charset=UTF-8" }) @ResponseBody public SeckillResult<SeckillExecution> execute( @PathVariable ( "seckillId" )Long seckillId, @PathVariable ( "md5" )String md5, @CookieValue (value= "phone" ,required= false )Long phone){ if (phone== null ){ return new SeckillResult<SeckillExecution>( false , "手机号未注册" ); } SeckillResult<SeckillExecution> result= null ; try { SeckillExecution execution=seckillService.executeSeckill(seckillId,phone,md5); result= new SeckillResult<SeckillExecution>( true ,execution); } catch (RepeatKillException e){ SeckillExecution execution= new SeckillExecution(seckillId,- 1 , "重复秒杀" ); result= new SeckillResult<SeckillExecution>( true ,execution); } catch (SeckillCloseException e){ SeckillExecution execution= new SeckillExecution(seckillId, 0 , "秒杀结束" ); result= new SeckillResult<SeckillExecution>( true ,execution); } catch (Exception e){ SeckillExecution execution= new SeckillExecution(seckillId,- 2 , "系统异常" ); result= new SeckillResult<SeckillExecution>( true ,execution); } return result; } //返回系统时间 @RequestMapping (value= "/time/now/" ,method = RequestMethod.GET) @ResponseBody public SeckillResult<Long> time(){ Date d= new Date(); return new SeckillResult<Long>( true ,d.getTime()); } } |
客户端实现
1、秒杀商品列表页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | <%@ page contentType= "text/html;charset=UTF-8" language= "java" %> <% @taglib prefix= "c" uri= "http://java.sun.com/jsp/jstl/core" %> <% @taglib prefix= "fmt" uri= "http://java.sun.com/jsp/jstl/fmt" %> <% @taglib prefix= "fn" uri= "http://java.sun.com/jsp/jstl/functions" %> <html> <head> <title>秒杀列表页</title> <meta name= "viewport" content= "width=device-width, initial-scale=1.0" > <meta charset= "utf-8" > <!-- 新 Bootstrap 核心 CSS 文件 --> <link href= "http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel= "stylesheet" > <!-- 可选的Bootstrap主题文件(一般不使用) --> <link href= "http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel= "stylesheet" > <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 --> <!-- 注意: 如果通过 file: // 引入 Respond.js 文件,则该文件无法起效果 --> <!--[ if lt IE 9 ]> <script src= "https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" ></script> <script src= "https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" ></script> <![endif]--> </head> <body> <div class = "container" > <div class = "panel panel-default" > <div class = "panel-heading text-center" > <h2>秒杀列表</h2> </div> <div class = "panel-body" > <table class = "table table-hover" > <thead> <tr> <th>名称</th> <th>库存</th> <th>开始时间</th> <th>结束时间</th> <th>创建时间</th> <th>秒杀</th> </tr> </thead> <tbody> <c:forEach var= "item" items= "${list}" > <tr> <td>${item.name}</td> <td>${item.number}</td> <td> <fmt:formatDate value= "${item.startTime}" pattern= "yyyy-MM-dd HH:mm:ss" /> </td> <td> <fmt:formatDate value= "${item.endTime}" pattern= "yyyy-MM-dd HH:mm:ss" /> </td> <td> <fmt:formatDate value= "${item.createTime}" pattern= "yyyy-MM-dd HH:mm:ss" /> </td> <td> <a class = "btn btn-info" href= "/seckill/${item.seckillId}/detail/" >秒杀</a> </td> </tr> </c:forEach> </tbody> </table> </div> </div> </div> </body> </html> |
2、秒杀商品详情页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | <%@ page contentType= "text/html;charset=UTF-8" language= "java" %> <% @taglib prefix= "c" uri= "http://java.sun.com/jsp/jstl/core" %> <% @taglib prefix= "fmt" uri= "http://java.sun.com/jsp/jstl/fmt" %> <% @taglib prefix= "fn" uri= "http://java.sun.com/jsp/jstl/functions" %> <html> <head> <title>秒杀详情</title> <meta name= "viewport" content= "width=device-width, initial-scale=1.0" > <meta charset= "utf-8" > <!-- 新 Bootstrap 核心 CSS 文件 --> <link href= "http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel= "stylesheet" > <!-- 可选的Bootstrap主题文件(一般不使用) --> <link href= "http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel= "stylesheet" > <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 --> <!-- 注意: 如果通过 file: // 引入 Respond.js 文件,则该文件无法起效果 --> <!--[ if lt IE 9 ]> <script src= "https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" ></script> <script src= "https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" ></script> <![endif]--> </head> <body> <div class = "container" > <div class = "panel panel-default text-center" > <div class = "pannel-heading" > <h1>${seckill.name}</h1> </div> <div class = "panel-body" > <h2 class = "text-danger" > <%--显示time图标--%> <span class = "glyphicon glyphicon-time" ></span> <%--展示倒计时--%> <span class = "glyphicon" id= "seckill-box" ></span> </h2> </div> </div> </div> <%--登录弹出层 输入电话--%> <div id= "killPhoneModal" class = "modal fade" > <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> <script src= "http://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js" ></script> <script src= "http://apps.bdimg.com/libs/bootstrap/3.3.0/js/bootstrap.min.js" ></script> <script src= "http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js" ></script> <script src= "http://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js" ></script> <script src= "/resources/scripts/seckill.js?201806242323235" ></script> <script type= "text/javascript" > $(function(){ seckill.detail.init({ seckillId:${seckill.seckillId}, startTime:${seckill.startTime.time}, //取毫秒数 endTime:${seckill.endTime.time} }) }) </script> </body> </html> |
3、秒杀业务逻辑seckill.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | var seckill={ /**秒杀相关url**/ URL:{ now: '/seckill/time/now/' }, /**验证手机号**/ validatePhone: function (phone){ if (phone && phone.length==11 && !isNaN(phone)){ return true ; } return false ; }, /**倒计时**/ countdown: function (seckillId,nowTime,startTime,endTime){ console.log(seckillId+ "," +nowTime+ "," +startTime+ "," +endTime); var seckillBox=$( "#seckill-box" ); if (nowTime>endTime){ seckillBox.html( "秒杀已经结束" ); } else if (nowTime<startTime){ //秒杀还没开始,显示倒计时 var killTime = new Date(startTime + 1000); seckillBox.countdown(killTime, function (e){ var format = e.strftime( '秒杀倒计时: %D天 %H时 %M分 %S秒 ' ); seckillBox.html(format); }).on( "finish.countdown" , function (){ console.log( "倒计时结束,开始秒杀" ); seckill.seckill(seckillId,seckillBox); }); } else { //秒杀开始 seckill.seckill(seckillId,seckillBox); } }, detail:{ /**初始化参数**/ init: function (params){ var phone=$.cookie( 'phone' ); //验证手机号 if (!seckill.validatePhone(phone)){ var killphoneModal=$( "#killPhoneModal" ); //如果有取到cookie里的手机,则弹出模拟登陆 killphoneModal.modal({ show: true , //显示弹出层 backdrop: 'static' , //禁止位置关闭 keyboard: false //关闭键盘事件 }); $( "#killPhoneBtn" ).click( function (){ var inputphone=$( "#killPhoneKey" ).val(); console.log( 'inputphone:' +inputphone); if (seckill.validatePhone(inputphone)){ $.cookie( "phone" ,inputphone,{expires:7,path: '/seckill' }); //验证通过,刷新页面 window.location.reload(); } else { $( '#killPhoneMessage' ).hide().html( '<label class="label label-danger">手机号错误!</label>' ).show(300); } }) } var seckillId=params[ "seckillId" ]; var startTime=params[ "startTime" ]; var endTime=params[ "endTime" ]; $.get(seckill.URL.now,{}, function (result){ if (result && result[ "success" ]){ var nowTime=result[ "data" ]; seckill.countdown(seckillId,nowTime,startTime,endTime); } else { console.log(result); } }) } }, /**执行秒杀**/ seckill: function (seckillId,node){ //获取秒杀地址、控制node节点显示,执行秒杀 node.hide().html( "<button id='killBtn' class='btn btn-primary btn-lg'>开始秒杀</button>" ) $.get( '/seckill/' +seckillId+ '/exposer' ,{}, function (result){ if (result && result[ "success" ]){ //在回调函数中执行秒杀操作 var exposer=result[ "data" ]; if (exposer[ "exposed" ]){ //秒杀已开始 var md5=exposer[ "md5" ]; var killUrl= '/seckill/' +seckillId+ '/' +md5+ '/execute' ; console.log(killUrl); $( "#killBtn" ).one( 'click' , function (){ //1、禁用秒杀按钮 $( this ).addClass( 'disabled' ); //2、执行秒杀操作 $.post(killUrl,{}, function (result){ if (result && result[ "success" ]){ var killResult=result[ "data" ]; var state=killResult[ "state" ]; var stateInfo=killResult[ "stateInfo" ]; node.html( "<span class='label label-success'>" +stateInfo+ "</span>" ); } }) }); 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); //没有拿到秒杀地址 } }) } } |
总结
秒杀相关业务逻辑主要是根据秒杀商品的开始时间、结束时间以及客户端的当前时间来判断秒杀是否开始、是否结束。未开始时调用jquery.countdown来实现倒计时效果。倒计时插件会维护一个倒计时事件,时间结束时直接会调用秒杀接口来实现秒杀业务。
作者:sword-successful
出处:https://www.cnblogs.com/sword-successful/p/9230446.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
博客地址: | http://www.cnblogs.com/sword-successful/ |
博客版权: | 本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。 如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步! 再次感谢您耐心的读完本篇文章。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步