系统总体架构
1.编写dao层
seckillDao接口
package com.hwj.dao;
import com.hwj.entity.Seckill;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
public interface SeckillDao {
//如果不用param这个注解 在传递多个参数的时候会出现绑定错误
int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime);
Seckill queryById(long seckillId);
List<Seckill> queryAll(@Param("offset") int offet, @Param("limit") int limit);
}
sucessSeckillDao接口
package com.hwj.dao;
import com.hwj.entity.SuccessKilled;
import org.apache.ibatis.annotations.Param;
public interface SuccessKilledDao {
//插入的行数 如返回0 插入失败
//执行添加操作后数据库给你的受影响行数的一个值,用于判断添加动作是否成功。
int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhpne);
}
注意事项:在进行传递参数的时候 如果大于一个参数,要用@Param注解进行辨别 在进行插入操作的时候将返回值的类型设为inti 用于判断添加动作是否成功
2.spring-dao.xml
在spring-dao.xml中主要分为三类配置
1.数据库连接池
2.sqlsessionfactory对象
3.配置扫描dao接口包 动态实现dao接口 注入到spring容器中
<?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"
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">
<!--数据库的配置文件一般方法在properties文件 方便修改-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--2.数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置连接池属性-->
<property name="driverClass" value="${jdbc.driver}" />
<!-- 基本属性 url、user、password -->
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!--c3p0私有属性-->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!--关闭连接后不自动commit-->
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<!--电脑太慢啦如果value是1000的话会出现错误!!!-->
<property name="checkoutTimeout" value="10000"/>
<!--当获取连接失败重试次数-->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!--3.配置SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--往下才是mybatis和spring真正整合的配置-->
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
<!--配置mybatis全局配置文件:mybatis-config.xml
classpath在maven项目里面是resources下面和java下面的目录-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--扫描entity包,使用别名,多个用;隔开-->
<property name="typeAliasesPackage" value="com.hwj.entity"/>
<!--扫描sql配置文件:mapper需要的xml文件-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--4:配置扫描Dao接口包,动态实现DAO接口,注入到spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描的Dao接口-->
<property name="basePackage" value="com.hwj.dao"/>
</bean>
<bean id="redisDao" class="com.hwj.dao.cache.RedisDao">
<constructor-arg index="0" value="localhost"></constructor-arg>
<constructor-arg index="1" value="6379"></constructor-arg>
</bean>
</beans>
2.编写service层
<?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"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描service包下所有使用注解的类型-->
<context:component-scan base-package="com.hwj.service"/>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置基于注解的声明式事务默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
SeckillServiceImpl实现类:
package com.hwj.service.impl;
import com.hwj.dao.SeckillDao;
import com.hwj.dao.SuccessKilledDao;
import com.hwj.dao.cache.RedisDao;
import com.hwj.dto.Exposer;
import com.hwj.dto.SeckillExecution;
import com.hwj.entity.Seckill;
import com.hwj.entity.SuccessKilled;
import com.hwj.enums.SeckillStatEnum;
import com.hwj.exception.RepeatKillException;
import com.hwj.exception.SeckillCloseException;
import com.hwj.exception.SeckillException;
import com.hwj.service.SeckillService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
//@Component 当你不知道他是service还是dao的时候使用
//@Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {
// private Logger logger= (Logger) LoggerFactory.getLogger(this.getClass());
//注入service依赖
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
@Autowired
private RedisDao redisDao;
private final String slat="213edfe*&%gweqd52";
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0,4);
}
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
//1.访问redis
Seckill seckill=redisDao.getSeckill(seckillId);
//2 如果redis中没有 访问数据库
if(seckill==null) {
System.out.println("访问数据库了!!");
seckill = seckillDao.queryById(seckillId);
//3 如果数据库中没有
if(seckill==null){
return new Exposer(false,seckillId);
}
else{
redisDao.putSeckill(seckill);
}
}
Date nowTime=new Date();
Date startTime=seckill.getStartTime();
Date endTime=seckill.getEndTime();
if(nowTime.getTime()<startTime.getTime()||nowTime.getTime()>endTime.getTime())
{
System.out.println("test2");
return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
}
String md5=getMd5(seckillId);
System.out.println("test3");
return new Exposer(true,md5,seckillId);
}
private String getMd5(long seckillId){
String base=seckillId+"/"+slat;
String md5= DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
//采用注解控制事物
@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
if(md5==null||!md5.equals(getMd5(seckillId))){
throw new SeckillException("error");
}
Date nowTime=new Date();
int insert=successKilledDao.insertSuccessKilled(seckillId,userPhone);
if(insert<=0) {
//重复秒杀
throw new RepeatKillException("repeat");
}else{
//减库存
int update=seckillDao.reduceNumber(seckillId,nowTime);
if(update<=0){
//没有更新到记录,秒杀结束
throw new SeckillCloseException("closed");
}
else{
//秒杀成功
SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,successKilled);
}
}
}
}
在编写service层时3 使用了dto包下的类
在实际项目中,DTO的分在两层传输,一层是service层向controller层,这一层的DTO封装service执行结果,给controller,简化业务数据,只提取部分业务相关字段或补充处理相关字段,不同业务可能有不同的此类dto,具有业务相关性;
另外一层是controller层向web前端层,这一层DTO封装controller执行结果,返回给前端web,请求返回实体封装类,适用于所有ajax请求返回的类型实体,无业务相关性
dto层的三个类:
Exposer类
秒杀开启时获取秒杀的加密地址,秒杀未开启时获取秒杀的开启时间和当前时间–在商品详情页上显示出来,给用户直观的显示。根据这个要求,我们需要创建一个实体类Exposer,放在dto包中,dto是数据传输层,关注web与service之间的数据传递。
package com.hwj.dto;
import java.util.Date;
/**
* Created by codingBoy on 16/11/27.
* 暴露秒杀地址(接口)DTO
*/
public class Exposer {
//是否开启秒杀
private boolean exposed;
//加密措施
private String md5;
private long seckillId;
//系统当前时间(毫秒)
private long now;
//秒杀的开启时间
private long start;
//秒杀的结束时间
private long end;
//写三个构造方法为了方便对象的初始化
public Exposer(boolean exposed, String md5, long seckillId) {
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}
public Exposer(boolean exposed, long seckillId,long now, long start, long end) {
this.exposed = exposed;
this.seckillId=seckillId;
this.now = now;
this.start = start;
this.end = end;
}
public Exposer(boolean exposed, long seckillId) {
this.exposed = exposed;
this.seckillId = seckillId;
}
public boolean isExposed() {
return exposed;
}
public void setExposed(boolean exposed) {
this.exposed = exposed;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public long getNow() {
return now;
}
public void setNow(long now) {
this.now = now;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
@Override
public String toString() {
return "Exposer{" +
"exposed=" + exposed +
", md5='" + md5 + '\'' +
", seckillId=" + seckillId +
", now=" + now +
", start=" + start +
", end=" + end +
'}';
}
}
seckillExecution类
执行秒杀操作,我们要成功秒杀一个商品,我们首先需要减库存,然后添加购买记录。如果秒杀成功,我们需要返回秒杀成功记录,如果失败,我们需要返回错误信息。因此,我们还需要一个实体类SeckillExcution,来保存返回的信息,实体类应有一个状态标识,和状态信息,来区分这两个成功或失败。另外,这两者要同时成功,或者同时失败,这就需要一个事务管理器来管理。
package com.hwj.dto;
import com.hwj.entity.SuccessKilled;
import com.hwj.enums.SeckillStatEnum;
/**
* 封装执行秒杀后的结果:是否秒杀成功
* Created by codingBoy on 16/11/27.
*/
public class SeckillExecution {
private long seckillId;
//秒杀执行结果的状态
private int state;
//状态的明文标识
private String stateInfo;
//当秒杀成功时,需要传递秒杀成功的对象回去
private SuccessKilled successKilled;
public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {
this.seckillId = seckillId;
this.state = statEnum.getState();
this.stateInfo = statEnum.getInfo();
this.successKilled = successKilled;
}
//秒杀失败
public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {
this.seckillId = seckillId;
this.state = statEnum.getState();
this.stateInfo = statEnum.getInfo();
}
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public SuccessKilled getSuccessKilled() {
return successKilled;
}
public void setSuccessKilled(SuccessKilled successKilled) {
this.successKilled = successKilled;
}
@Override
public String toString() {
return "SeckillExecution{" +
"seckillId=" + seckillId +
", state=" + state +
", stateInfo='" + stateInfo + '\'' +
", successKilled=" + successKilled +
'}';
}
}
SeckillResult类:
package com.hwj.dto;
/**
* Created by codingBoy on 16/11/28.
*/
//将所有的ajax请求返回类型,全部封装成json数据
public class SeckillResult<T> {
//请求是否成功
private boolean success;
private T data;
private String error;
public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}
public SeckillResult(boolean success, String error) {
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;
}
}
3. 编写web
<?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: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">
<!--配置spring mvc-->
<!--1,开启springmvc注解模式
a.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
b.默认提供一系列的功能:数据绑定,数字和日期的format@NumberFormat,@DateTimeFormat
c:xml,json的默认读写支持-->
<mvc:annotation-driven/>
<!--2.静态资源默认servlet配置-->
<!--
1).加入对静态资源处理:js,gif,png
2).允许使用 "/" 做整体映射
-->
<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"/>
</bean>
<!--4:扫描web相关的controller-->
<context:component-scan base-package="com.hwj.web"/>
</beans>
SeckillController类
package com.hwj.web;
import com.hwj.dto.Exposer;
import com.hwj.dto.SeckillExecution;
import com.hwj.dto.SeckillResult;
import com.hwj.entity.Seckill;
import com.hwj.enums.SeckillStatEnum;
import com.hwj.exception.RepeatKillException;
import com.hwj.exception.SeckillCloseException;
import com.hwj.service.SeckillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/seckill")//:/模块/资源/{id}
public class SeckillController {
@Autowired
private SeckillService seckillService;
@RequestMapping(value="/list",method= RequestMethod.GET)
public String list(Model model){
List<Seckill> list=seckillService.getSeckillList();
model.addAttribute("list",list);
return "list";
}
@RequestMapping(value="/{seckillId}/detail",method=RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckilllId, Model model){
System.out.println("test");
if(seckilllId==null){
return "redirect:/seckill/list";
}
Seckill seckill=seckillService.getById(seckilllId);
if(seckill==null){
return "forward:/seckill/list";
}
model.addAttribute("seckill",seckill);
return "detail";
}
//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;
try{
Exposer exposer=seckillService.exportSeckillUrl(seckillId);
result=new SeckillResult<Exposer>(true,exposer);
}catch (Exception e)
{
e.printStackTrace();
result=new SeckillResult<Exposer>(false,e.getMessage());
}
return result;
}
@RequestMapping(value = "/{seckillId}/{md5}/execution",
method = RequestMethod.POST,
produces = {"application/json;charset=UTF-8"})
@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<SeckillExecution>(false,"未注册");
}
SeckillResult<SeckillExecution> result;
try {
SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
return new SeckillResult<SeckillExecution>(true, execution);
}catch (RepeatKillException e1)
{
SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true,execution);
}catch (SeckillCloseException e2)
{
SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<SeckillExecution>(true,execution);
}
catch (Exception e)
{
SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true,execution);
}
}
//获取系统时间
@RequestMapping(value = "/time/now",method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Long> time()
{
Date now=new Date();
return new SeckillResult<Long>(true,now.getTime());
}
}
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<!--用maven创建的web-app需要修改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
Mybites -> 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>
4 前端
<%@page contentType="text/html; charset=UTF-8" language="java" %>
<%@include file="common/tag.jsp"%>
<!DOCTYPE html>
<html>
<head>
<title>秒杀商品列表</title>
<%@include file="common/head.jsp" %>
</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 items="${list}" var="sk">
<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">详情</a></td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://apps.bdimg.com/libs/bootstrap/3.3.0/js/bootstrap.min.js"></script>
</body>
</html>
<%@page contentType="text/html; charset=UTF-8" language="java" %>
<%@include file="common/tag.jsp" %>
<!DOCTYPE html>
<html>
<head>
<title>秒杀详情页</title>
<%@include file="common/head.jsp" %>
</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>
<%--登录弹出层 输入电话--%>
<%--modal fade默认是隐藏的--%>
<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>
<%--jQery文件,务必在bootstrap.min.js之前引入--%>
<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>
<%--使用CDN 获取公共js http://www.bootcdn.cn/--%>
<%--jQuery Cookie操作插件--%>
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<%--jQuery countDown倒计时插件--%>
<script src="http://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},//毫秒 转换成毫秒 方便js做解析
endTime:${seckill.endTime.time}
});
})
</script>
</html>
seckill.js
//存放主要交互逻辑的js代码
// javascript 模块化(package.类.方法)
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;//直接判断对象会看对象是否为空,空就是undefine就是false; isNaN 非数字返回true
} else {
return false;
}
},
//详情页秒杀逻辑
detail: {
//详情页初始化
//params这个参数没有类型吗 是随便写的吗???
init: function (params) {
//手机验证和登录,计时交互
//规划我们的交互流程
//在cookie中查找手机号
//$.cookie('userPhone');读取cookie
var userPhone = $.cookie('userPhone');
//验证手机号
//没有登录
//为什么调用函数全部都要加seckill
if (!seckill.validatePhone(userPhone)) {
//绑定手机 控制输出
//得到弹出层的节点
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show: true,//显示弹出层,detail.jsp中弹出层默认是隐藏的
backdrop: 'static',//禁止位置关闭
keyboard: false//关闭键盘事件
});
// 弹出层的提交按钮做绑定操作
$('#killPhoneBtn').click(function () {
var inputPhone = $('#killPhoneKey').val();
console.log("inputPhone: " + inputPhone);
if (seckill.validatePhone(inputPhone)) {
//电话写入cookie(7天过期)
$.cookie('userPhone', inputPhone, {expires: 7, path: '/seckill'});
//验证通过 刷新页面
window.location.reload();
} else {
//todo 错误文案信息抽取到前端字典里
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
});
}
//已经登录
//计时交互
var startTime = params['startTime'];
var endTime = params['endTime'];
var seckillId = params['seckillId'];
//是一个简单的 GET 请求功能以取代复杂 $.ajax 。请求成功时可调用回调函数
//语法:$(selector).get(url,data,success(response,status,xhr),dataType)
$.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);
alert('result: ' + result);
}
});
}
},
handlerSeckill: function (seckillId, node) {
//获取秒杀地址,控制显示器,执行秒杀
node.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
$.get(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一直绑定点击事件 防止用户一直点击事件 向服务器发送大量请求
$('#killBtn').one('click', function () {
//执行秒杀请求
//1.先禁用按钮
//this==#killBtn在哪执行 this代表哪个对象
$(this).addClass('disabled');//,<-$(this)===('#killBtn')->
//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);
}
});
},
// 将时间的比较封装成一个函数
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);//todo 防止时间偏移
//function(event)是一个回调函数???不懂??
seckillBox.countdown(killTime, function (event) {
//时间格式
var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒 ');
seckillBox.html(format);
}).on('finish.countdown', function () {
//时间完成后回调事件
//获取秒杀地址,控制显示逻辑,执行秒杀
console.log('______fininsh.countdown');
seckill.handlerSeckill(seckillId, seckillBox);
});
} else {
//秒杀开始
seckill.handlerSeckill(seckillId, seckillBox);
}
}
}