慕课网《Java高并发秒杀API之web层》学习总结
时间:2017年08月24日星期四
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/study-imooc
第一章:设计Restful接口
1-1 交互流程
学习本门课程前请先学习前置课程《Java高并发秒杀API之业务分析与DAO层》和《Java高并发秒杀API之Service层》
主要内容
前端交互设计
Restful
SpringMVC
Bootstrap + jQuery
前端页面流程
详情页流程逻辑
1-2 Restful接口什么是Restful
兴起于Rails
一种优雅的URL表述方式
资源的状态和状态转移
Restful示例
GET /seckill/list 表述秒杀的列表页
POST /seckill/{seckillId}/execution 表述该资源执行execution操作
DELETE /seckill/{id}/delete 表述该资源执行delete操作即删除
Restful规范
GET:查询操作
POST:添加/修改操作
PUT:修改操作
DELETE:删除操作
URL设计
/模块/资源/{标识}/集合1/…
/user/{uid}frends:好友列表
/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:执行秒杀
第二章:SpringMVC整合Spring
2-1 SpringMVC理论
SpringMVC始终围绕Handler开发
数据(Model)
页面(View)
SpringMVC运行流程
HTTP请求地址映射原理
注解映射技巧
@RequestMapping注解
(1)支持标准的URL
(2)Ant风格URL(?:匹配一个字符;*:匹配任意字符;**:匹配任意URL路径)
(3)带{xxx}占位符的URL
例如:
/user/*/creation
匹配/user/aaa/creation、/user/bbb/creation等URL
/user/**/creation
匹配/user/creation、/user/aaa/bbb/creation等URL
/user/{userId}
匹配/user/123、/user/abc等URL
/company/{companyId}/user/{userId}/detail
匹配/company/123/user/456/detail等URL
请求方法细节处理
1.请求参数绑定
2.请求方式限制
3.请求转发和重定向
4.数据模型赋值
5.放回json数据
6.cookie访问
举个例子
返回json数据
Cookie访问
2-2 SpringMVC配置代码编写
1.修改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">
<!-- 修改servlet版本为3.1 -->
<!-- <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> -->
<!-- 配置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>
2.编写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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置SpringMVC -->
<!-- 1:开启SpringMVC注解模式 -->
<!-- 简化配置:
(1)自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
(2)提供一系列:数据绑定,数字和日期的format,@NumberFormat,@DataTimeFormat
xml,json默认读写支持
-->
<mvc:annotation-driven />
<!-- 2:servlet-mapping 映射路径:"/" -->
<!-- (1)静态资源默认servlet配置
(2)允许使用"/"做整体映射
-->
<mvc:default-servlet-handler/>
<!-- 3:配置jsp显示ViewResolver -->
<bean id="viewResolver" 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"/>
</bean>
<!-- 4:扫描web相关的bean -->
<context:component-scan base-package="com.myimooc.seckill.web"/>
</beans>
第三章:实现Restful接口
3-1 使用SpringMVC实现(上)
代码编写
1.编写SeckillController类
/**
* @describe 控制器
* @author zc
* @version 1.0 2017-08-24
*/
@Controller
@RequestMapping(value = "/seckill")// url:/模块/资源/{id}/细分
public class SeckillController {
public SeckillController(){
System.out.println("SeckillController被初始化");
}
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
// 该注解为组合注解,等价于 @RequestMapping(name="/list",method=RequestMethod.GET)
@GetMapping(value = "/list")
public ModelAndView list(Model model){
logger.info("进入列表页");
// 获取列表页
List<Seckill> list = seckillService.getSeckillList();
logger.info("list = {}",list);
model.addAttribute(list);
// list.jsp + model = ModelAndView
return new ModelAndView("list").addObject("list",list); // /WEB-INF/jsp/list.jsp
}
@GetMapping("/{seckillId}/detail")
public String detail(@PathVariable("seckillId") Long seckillId, Model model){
if(null == seckillId){
// 重定向
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
if(null == seckill){
// 请求转发
return "forward:/seckill/list";
}
model.addAttribute("seckill",seckill);
return "detail";
}
// ajax json
@PostMapping(value = "/{seckillId}/exposer",produces = {"application/json;charset=UTF-8"})
@ResponseBody
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;
}
@PostMapping(value = "/{seckillId}/{md5}/execution",produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId")Long seckillId,
@PathVariable("md5")String md5,
@CookieValue(value = "killPhone",required = false) Long phone){
if(StringUtils.isEmpty(phone)){
return new SeckillResult<SeckillExecution>(false,"未注册");
}
// SeckillResult<SeckillExecution> result;
try{
// 存储过程调用
SeckillExecution execution = seckillService.executeSeckill(seckillId,phone,md5);
//SeckillExecution execution = seckillService.executeSeckill(seckillId,phone,md5);
return new SeckillResult<SeckillExecution>(true,execution);
} catch (SeckillCloseException e1){
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<SeckillExecution>(true,execution);
} catch (RepeatKillException e2){
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true,execution);
} catch (Exception e){
logger.error(e.getMessage(),e);
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true,execution);
}
}
@GetMapping("/time/now")
@ResponseBody
public SeckillResult<Long> time(){
Date now = new Date();
return new SeckillResult<Long>(true,now.getTime());
}
}
3-2 使用SpringMVC实现(下)
代码编写
1.编写SeckillResult类
package com.myimooc.seckill.dto;
/**
* @describe VO 对象,所有ajax请求返回类型,封装json结果
* @author zc
* @version 1.0 2017-08-24
*/
public class SeckillResult<T> {
private boolean success;
private T data;
private String error;
// 省略getter and setter 和 构造方法
}
第四章:使用Bootstrap
4-1 基于Bootstrap开发页面
代码编写
1.编写head.jsp
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!-- Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
2.编写tag.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
3.编写list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 引入jstl -->
<%@include file="common/tag.jsp"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>秒杀列表页</title>
<%@include file="common/head.jsp"%>
</head>
<body>
<!-- 页面显示部分 -->
<div class="container">
<div class="panel panel-default">
<div class="panel-heading text-center">
<h1>秒杀列表</h1>
</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="sk" items="${list}">
<tr>
<td>${sk.name}</td>
<td>${sk.number}</td>
<td><fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a> </td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>
4.编写detail.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>秒杀详情页</title>
<%@include file="common/head.jsp"%>
</head>
<body>
<div class="container">
<div class="panel panel-default text-center">
<div class="panel-heading">${seckill.name}</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="填手机号" 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>
</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<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 (){
// 使用EL表达式传入参数
seckill.detail.init({
seckillId : ${seckill.seckillId},
startTime : ${seckill.startTime.time},//毫秒
endTime : ${seckill.endTime.time}
});
});
</script>
</html>
第五章:交互逻辑
5-1 交互实现
代码编写
1.编写seckill.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';
}
},
// 处理秒杀逻辑
handleSeckillKill : 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);
// 绑定一次点击事件
$('#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'];
// 3:显示秒杀结果
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.mycountdown(seckillId,now,start,end);
}
}else{
console.log('result:'+result);
}
});
},
// 验证手机号
validatePhone:function (phone) {
if(phone && phone.length == 11 && !isNaN(phone)){
return true;
}else{
return false;
}
},
mycountdown : function(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(event){
// 时间格式
var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒');
seckillBox.html(format);
// 时间完成后回调时间
}).on('finish.countdown',function () {
// 获取秒杀地址,控制显示逻辑,执行秒杀
seckill.handleSeckillKill(seckillId,seckillBox);
});
}else {
// 秒杀开始
seckill.handleSeckillKill(seckillId,seckillBox);
}
},
// 详情页秒杀逻辑
detail:{
// 详情页初始化
init : function (params) {
// 手机验证和登录 , 计时交互
// 规划我们的交互流程
// 在cookie中查找手机号
var killPhone = $.cookie('killPhone');
// 验证手机号
if(!seckill.validatePhone(killPhone)){
// 绑定phone
// 控制输出
var killPhoneModal = $('#killPhoneModal');
// 显示弹出层
killPhoneModal.modal({
//显示弹出层
show:true,
// 禁止位置关闭
backdrop:'static',
// 关闭键盘事件
keyboard:false
});
$('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val();
console.log('inputPhone='+inputPhone);//TODO
if(!seckill.validatePhone(killPhone)){
// 电话写入cookie
$.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'});
// 刷新页面
window.location.reload();
}else {
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
});
}
// 已经登录
// 计时交互
var startTime = params['startTime'];
var endTime = params['endTime'];
var seckillId = params['seckillId'];
$.get(seckill.URL.now(),{},function(result){
if(result &&result['success']){
var nowTime = result['data'];
// 时间判断,计时交互
seckill.mycountdown(seckillId,nowTime,startTime,endTime);
}else{
console.log('result:'+result);
}
});
}
}
}
第六章:课程总结
6-1 总结
技术回顾
前端交互设计过程
Restful接口设计
SpringMVC使用技巧
Bootstrap和JS使用