1、首先引入springMVC
~、修改web.xml,引入springMVC的DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>imoocDemo1</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 修改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
-->
<!-- 此处如果不配置的话,spring会默认的去WEB-INF下去查找默认规范的配置文件 -->
<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>
2、编写Controller
Controller // 类似与@Service和@Component
@RequestMapping("/seckill") // url:/模块/资源/{id}/细分
public class SeckillController {
// private org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());
public SeckillController() {
}
@Autowired
private SeckillService seckillService;
/**
* 进入秒杀列表
*
* @param model 模型数据,存放秒杀商品的信息
* @return 秒杀列表详情页面
*/
@RequestMapping(value = "/{showPage}/list", method = RequestMethod.GET)
public String list(@PathVariable("showPage") int showPage, Model model) {
System.out.println("经过controler来到list界面");
List<Seckill> seckillList = seckillService.getSeckillList(showPage);
int pageCount = seckillService.getPageCount();
model.addAttribute("list", seckillList);
model.addAttribute("pageCount", pageCount);
return "list"; // WEN-INF/list.jsp
}
@RequestMapping(value = "/{showPage}/listPage", method = RequestMethod.GET)
@ResponseBody
public List<Seckill> pageList(@PathVariable("showPage") int showPage, Model model) {
List<Seckill> seckillList = seckillService.getSeckillList(showPage);
Iterator iterator = seckillList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("=======================");
model.addAttribute("list", seckillList);
return seckillList; // WEN-INF/list.jsp
}
/**
* 根据id搜索商品,展示商品
*
* @param seckillId
* @param model
* @return
*/
@RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckillId, Model model) {
if (seckillId == null) {
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
if (seckill == null) {
return "forward:/seckill/list";
}
model.addAttribute("seckill", seckill);
return "detail";
}
/**
* 暴露秒杀的接口的方法
*
* @param seckillId
* @return 根据用户秒杀的商品id进行业务逻辑判断,返回不同的json实体结果
*/
@RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST)
@ResponseBody // 告诉springmvc,返回的类型作为ajax输出
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") 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<Exposer>(false, e.getMessage());
}
return result;
}
/**
* 用户执行秒杀,在页面点击相关的秒杀连接,进入后获取对应的参数进行判断 返回响应的json实体结果,前端在进行处理
*
* @param seckillId 秒杀的商品,对应的秒杀时的id
* @param md5 一个被盐值加密过的md5加密值
* @param userPhone 参与秒杀用户的手机号码,当做账号密码使用
* @return 参与秒杀的结果,为json数据
*/
@RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST)
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") long seckillId,
@PathVariable("md5") String md5, @CookieValue(value = "userPhone", required = false) Long userPhone) {
// 如果用户的手机号码为空,说明用户没有完成注册
if (userPhone == null) {
return new SeckillResult<>(false, "没有注册");
}
try {
// 这里换成储存过程
//SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, userPhone, md5);
return new SeckillResult<>(true, execution);
} catch (RepeaKillException e1) {
// 重复秒杀
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
return new SeckillResult<>(false, execution);
} catch (SeckillCloseException e2) {
// 秒杀关闭
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
return new SeckillResult<>(false, execution);
} catch (SeckillException e) {
// 不能判断的异常
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
return new SeckillResult<>(false, execution);
}
}
@RequestMapping(value = "/time/now", method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Date> time() {
Date date = new Date();
return new SeckillResult<>(true, date);
}
}
3、建立一个全局ajax请求返回类,返回json数据
com.seckill.dto.SeckillResult
package com.seckill.dto;
/**
* 封装所有的ajax请求返回类型,方便返回json
*
* @author hyh47
*
*/
public class SeckillResult<T> {
//执行的结果
private boolean success;
//泛型数据
private T data;
//字符串类型的具体error
private String error;
public SeckillResult(boolean success, T data) {
super();
this.success = success;
this.data = data;
}
public SeckillResult(boolean success, String error) {
super();
this.success = success;
this.error = error;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
@Override
public String toString() {
return "SeckillResult [success=" + success + ", data=" + data + ", error=" + error + "]";
}
}
4、前台页面的编写
image.png对于jQuery和Bootstrap的引入,可以使用本地路径方式和CDN链接两种引入方式
首先需要一个列表页面:
<%@page import="com.fasterxml.jackson.annotation.JsonInclude.Include" %>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
<%@include file="common/tag.jsp" %>
<html lang="zh-CN">
<head>
<%@include file="common/head.jsp" %>
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>秒杀列表页面</title>
<script type="text/javascript" src="<%=path%>/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="<%=path%>/js/jquery.paginate.js"></script>
<script type="text/javascript" src="<%=path%>/js/bootstrap.min.js"></script>
<style>
.demo {
width: 580px;
padding: 10px;
margin: 10px auto;
border: 1px solid #fff;
background-color: #f7f7f7;
}
</style>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading text-center">
<h2>秒杀列表</h2>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<tr>
<th>名称</th>
<th>库存</th>
<th>开始时间</th>
<th>结束时间</th>
<th>创建时间</th>
<th>详情页</th>
</tr>
</thead>
<tbody>
<div id="listTable">
<c:forEach items="${list}" var="sk" varStatus="skStatus">
<tr>
<td id="name${skStatus.index}">${sk.name}</td>
<td id="number${skStatus.index}">${sk.number}</td>
<td id="startTime${skStatus.index}"><fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td id="endTime${skStatus.index}"><fmt:formatDate value="${sk.endTime }" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td id="createTime${skStatus.index}"><fmt:formatDate value="${sk.createTime }" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><a id="seckillHref" class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a></td>
</tr>
</c:forEach>
</div>
</tbody>
</table>
<div id="paginationdemo" class="demo">
<div id="demo4"></div>
</div>
</div>
</div>
<script type="text/javascript">
$(function () {
$("#demo4").paginate({
count: ${pageCount},
start: 1,
display: 5,
border: false,
text_color: '#79B5E3',
background_color: 'none',
text_hover_color: '#2573AF',
background_hover_color: 'none',
images: false,
mouse: 'press',
onChange: function (page) {
//$('._current','#paginationdemo').removeClass('_current').hide();
//$('#p'+page).addClass('_current').show();
$('._current', '#paginationdemo').text("page" + page);
//此处使用ajax.post方法进行访问
$.get("/seckill/" + page + "/listPage", {}, function (result) {
var temp;
for (var i = 4; i >= 0; i--) {
temp = result[i];
if (temp != null) {
var startTime = new Date(temp['startTime']);
var endTime = new Date(temp['endTime']);
var createTime = new Date(temp['createTime']);
var seckillId = temp['seckillId'];
console.log(seckillId);
$('#name' + i).text(temp['name']);
$('#number' + i).text(temp['number']);
$('#startTime' + i).text(startTime.toLocaleString());
$('#endTime' + i).text(endTime.toLocaleString());
$('#createTime' + i).text(createTime.toLocaleString());
document.getElementById("seckillHref").setAttribute("href", "/seckill/" + seckillId + "/detail");
} else {
$('#name' + i).text(null);
$('#number' + i).text(null);
$('#startTime' + i).text(null);
$('#endTime' + i).text(null);
$('#createTime' + i).text(null);
}
}
});
}
});
});
</script>
</body>
</html>
此页面中使用了jquery.paginate.js的分页插件http://www.jq22.com/jquery-info34
然后在前面部分对于一些通用的JSP设置和静态文件(CSS等)的导入,采用了JSP的静态导入:
<%@include file="common/tag.jsp" %>、<%@include file="common/head.jsp" %>
而相应的jsp如下:
tag.jsp
<%@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" %>
head.jsp:
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 检测设备终端的宽度和高度 -->
<meta charset="utf-8">
<link rel="stylesheet" href="<%=path%>/css/bootstrap.min.css"
type="text/css">
<link rel="stylesheet" href="<%=path%>/css/bootstrap-theme.min.css"
type="text/css">
<link rel="stylesheet" href="<%=path%>/css/style.css" type="text/css">
点击商品详情后,需要的是商品秒杀详情页面:
detail.jsp:
<%@page import="com.fasterxml.jackson.annotation.JsonInclude.Include" %>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
<%@include file="common/tag.jsp" %>
<html lang="zh-CN">
<head>
<%@include file="common/head.jsp" %>
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>秒杀详情页面</title>
<script type="text/javascript" src="<%=path%>/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="<%=path%>/js/bootstrap.min.js"></script>
<script type="text/javascript"
src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script type="text/javascript"
src="https://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>
<!-- <script type="text/javascript"
src="https://cdn.bootcss.com/bootstrap-modal/2.2.6/css/bootstrap-modal-bs3patch.min.css"></script>
</head> -->
<body>
<div class="container">
<div class="panel panel-default text-center">
<div class="panel-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="userPhoneModal" 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>
<div class="modal-body">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" name="userPhone" id="userPhoneKey"
placeholder="填写手机号码" class="form-control">
</div>
</div>
</div>
<div class="modal-footer">
<span id="userPhoneMessage" class="glyphicon"></span>
<button type="button" id="userPhoneBtn" class="btn btn-success">
<span class="glyphicon glyphicon-phone"></span> 提交
</button>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="<%=path%>/js/seckill.js"></script>
<script type="text/javascript">
//采用jquery自动加载$(function(){});
//初始化init,init内部存在着秒杀的逻辑
$(function () {
//详情页初始化
seckill.detail.init({
//采用jstl获取需要的数据,作为init函数的参数
//在函数内部获取参数的形式为: param['seckillId']
seckillId: ${seckill.seckillId},
startTime: ${seckill.startTime.time},//毫秒
endTime: ${seckill.endTime.time}
});
});
</script>
</html>
detail.jsp中再执行过程中相应的一些js逻辑:
//存放主要交互逻辑js代码
/**
* 使用模块化javascript
*/
var seckill = {
//封装秒杀相关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";
}
},
validatePhone: function (phone) {
if (phone && phone.length == 11 && !isNaN(phone)) {
return true;
} else {
return false;
}
},
//详情页秒杀逻辑
detail: {
//详情页初始化
init: function (params) {
//手机验证和登录,计时交互
//规划系列交互流程
//在cookies中查找手机号
var userPhone = $.cookie("userPhone");
if (!seckill.validatePhone(userPhone)) {
console.log("为填写手机号码");
var userPhoneModal = $("#userPhoneModal");
/*
modal的选项:
backdrop(boolean、或者String'static')指定一个静态场景,
当用户点击模态框外部时不会关闭模态框
keyboard(boolean):当按下escape键时关闭模态框,设置为false时则按键失效
show(boolean):当初始化时显示模态框
*/
userPhoneModal.modal({
show: true, //显示弹出层
backdrop: 'static', //精致位置关闭
keyboard: false //关闭键盘事件
});
/*
$.cookie(名称,值,[option])
[option]参数说明:
expires:有限日期,可以是一个整数或一个日期(单位:天).默认关闭浏览器则丢失
path:cookie值保存的路径,默认与创建页路径一致
domin:cookie域名属性,默认与创建页域名一样。这个地方要相当注意,跨域的概念,如果要主域名二级域名有效则要设置 ".xxx.com"
secrue:一个布尔值,表示传输cookie值时,是否需要一个安全协议。
*/
$("#userPhoneBtn").click(function () {
console.log("提交手机号码按钮被点击");
var inputPhone = $("#userPhoneKey").val(); //获得输入的手机号码
console.log("inputPhone" + inputPhone);
if (seckill.validatePhone(inputPhone)) {
//把手机号码写入cookie
$.cookie('userPhone', inputPhone, {
//expires : 7,//有效日期,默认为关闭浏览器则失效
path: '/seckill'
})
window.location.reload();
} else {
$("#userPhoneMessage").hide().html("<label class='label label-danger'>手机号码错误</label>").show(1000);
}
});
} else {
console.log("在cookie中找到了手机号码");
//已经登录了就开始进行计时交互
var startTime = params['startTime'];
var endTime = params['endTime'];
var seckillId = params['seckillId'];
console.log("开始秒杀时间:" + startTime + " 时间格式:" + startTime.time);
console.log("结束秒杀时间:" + endTime);
$.get(seckill.URL.now(), function (result) {
if (result && result['success']) {
var nowTime = result['data'];
console.log("服务器当前时间:" + nowTime);
seckill.countdown(seckillId, nowTime, startTime, endTime);
} else {
console.log("结果:" + result);
}
});
}
;
}
},
handlerSeckill: function (seckillId, mode) {
//获得秒杀地址
mode.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
console.debug("开始进行秒杀地址获取");
$.post(seckill.URL.exposer(seckillId), {}, function (result) {
//在回调函数中,执行交互流程
if (result && result['success']) {
var exposer = result['data'];
if (exposer['exposed']) {
console.log("有秒杀地址接口");
//开启秒杀,获取秒杀地址
var md5 = exposer['md5'];
var killUrl = seckill.URL.execution(seckillId, md5);
console.log("秒杀的地址为:" + killUrl);
//绑定一次点击事件,使用one,防止用户连续点击按钮,连续发送按钮请求
$("#killBtn").one('click', function () {
console.log("开始进行秒杀,按钮被禁用");
//执行秒杀请求,先禁用按钮
$(this).addClass("disabled");
//发送秒杀请求
$.post(killUrl, {}, function (result) {
console.info(result);
var killResult = result['data'];
console.log(killResult['seckillId']);
var state = killResult['state'];
var stateInfo = killResult['stateInfo'];
console.log("秒杀状态:" + stateInfo);
//显示秒杀结果
mode.html('<span class="label label-success">' + stateInfo + '</span>');
});
});
mode.show();
} else {
console.warn("还没有暴露秒杀地址接口,无法进行秒杀");
//未开启秒杀
var now = exposer['now']; //
var start = exposer['start'];
var end = exposer['end'];
//重新计算计时逻辑
seckill.countdown(seckillId, now, start, end);
}
} else {
console.log("result:" + result);
}
});
},
//倒计时交互
countdown: function (seckillId, nowTime, startTime, endTime) {
console.log("秒杀的商品ID:" + seckillId + ",服务器当前时间:" + nowTime + ",开始秒杀的时间:" + startTime + ",结束秒杀的时间" + endTime);
var seckillBox = $("#seckill-box");
//获取时间戳进行实践的比较
nowTime = new Date(nowTime).valueOf();
startTime = new Date(startTime).valueOf();
endTime = new Date(endTime).valueOf();
console.log("转换后的Date类型当前时间戳" + nowTime);
console.log("转换后的Date类型开始时间戳" + startTime);
console.log("转换后的Date类型结束时间戳" + endTime);
if (nowTime < endTime && nowTime > startTime) {
console.log("秒杀可以开始,时间条件符合");
seckill.handlerSeckill(seckillId, seckillBox);
}
else if (nowTime > endTime) {
console.log("秒杀时间已经结束");
seckillBox.html("秒杀结束");
} else if (nowTime < startTime) {
console.log("秒杀还没有开始");
//秒杀未开启,计时事件绑定
var killTime = new Date(startTime + 1000);
console.log(killTime);
console.log("开始计时效果");
seckillBox.countdown(killTime, function (event) {
//事件格式
var format = event.strftime('秒杀倒计时:%D天 %H时 %M分 %S秒');
console.log(format);
seckillBox.html(format);
/*时间完成后回调事件*/
}).on('finish.countdown', function () {
console.log("准备执行回调,获取秒杀地址,执行秒杀");
console.log("倒计时结束");
seckill.handlerSeckill(seckillId, seckillBox);
});
}
}
}
网友评论