SSM框架搭建秒杀项目
SSM框架搭建秒杀项目
此项目为慕课网上yijun zhang老师的课程。
项目展示:
(1)秒杀列表页:
(2)秒杀详情页(输入手机号,验证成功,才可执行秒杀)
(3)秒杀详情页(执行秒杀)
(4)秒杀详情页(秒杀成功)
(5)秒杀详情页(重复秒杀)
(6)秒杀详情页(秒杀倒计时)
(7)秒杀详情页(秒杀结束)
课程分为四个部分:
Java高并发秒杀API之业务分析与DAO层
Java高并发秒杀API之Service层
点击打开链接Java高并发秒杀API之web层
Java高并发秒杀API之高并发优化
本项目使用到的开发工具:eclipse,Navicat,tomcat
分析秒杀业务:
秒杀业务的核心为:对库存的处理。
用户针对库存业务的分析:
购买行为:谁购买成功了,什么时间购买,付款/发货信息
MySQL实现秒杀难点分析:高并发主要发生在多个用户对一个产品的秒杀,产生一种竞争,这个竞争反映到MySQL背后的技术为事务+行级锁。
事务:Start Transaction ,update库存数量,insert购买明细,Commit(红色字体为主要竞争)
行级锁:
秒杀的难点为:如何高效的处理竞争。
实现秒杀功能:秒杀接口暴露,实行秒杀,相关查询。
使用eclipse创建maven-web工程。
在pom.xml中添加相关插件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.seckill</groupId>
<artifactId>seckill</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>org.seckill Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--补全项目依赖 -->
<!--1.日志 java日志有:slf4j,log4j,logback,common-logging slf4j:是规范/接口 日志实现:log4j,logback,common-logging
使用:slf4j+logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.1</version>
</dependency>
<!--实现slf4j接口并整合 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
<!--1.数据库相关依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.1</version>
</dependency>
<!--2.dao框架:MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!--mybatis自身实现的spring整合依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!--3.Servlet web相关依赖 -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.56</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>7.0.57</version>
<scope>provided</scope>
</dependency>
<!-- <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId>
<scope>provided</scope> <version>2.0</version> </dependency> -->
<!--4:spring依赖 -->
<!--1)spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--2)spring dao层依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--3)springweb相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--4)spring test相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--添加redis依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<!--prostuff序列化依赖 -->
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<finalName>seckill</finalName>
</build>
</project>
第一部分:Java高并发秒杀API之业务分析与DAO层
1.设计数据库编码
-- 数据库初始化脚本
-- 创建数据库
CREATE DATABASE seckill;
-- 使用数据库
use seckill;
-- 创建秒杀库存表
CREATE TABLE seckill(){
`seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID',
`name` VARCHAR(120) NOT NULL COMMENT '商品名称',
`number` int NOT NULL COMMENT '库存数量',
`start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
`end_time` TIMESTAMP NOT NULL COMMENT '秒杀结束时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (seckill_id),
key idx_start_time(start_time),
key idx_end_time(end_time),
key idx_create_time(create_time)
}ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';
--初始化数据
INSERT into seckill(name,number,start_time,end_time)
VALUES
('1000元秒杀iphone6',100,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('800元秒杀ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('6600元秒杀mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('7000元秒杀iMac',400,'2016-01-01 00:00:00','2016-01-02 00:00:00');
-- 秒杀成功明细表
-- 用户登录认证相关信息(简化为手机号)
CREATE TABLE success_killed(
`seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID',
`user_phone` BIGINT NOT NULL COMMENT '用户手机号',
`state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货',
`create_time` TIMESTAMP NOT NULL COMMENT '创建时间',
PRIMARY KEY(seckill_id,user_phone),/*联合主键*/
KEY idx_create_time(create_time)
)ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
-- SHOW CREATE TABLE seckill;#显示表的创建信息
使用Navicat添加相关表。
2.在src/main/java中创建与数据库表对应的entity包
(1)Seckill.java
package org.seckill.entity;
import java.util.Date;
public class Seckill {
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime
+ ", endTime=" + endTime + ", createTime=" + createTime + "]";
}
}
(2)SuccessKilled.java
package org.seckill.entity;
import java.util.Date;
public class SuccessKilled {
private long seckillId;
private long userPhone;
private short state;
private Date createTime;
//多对一
private Seckill seckill;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public long getUserPhone() {
return userPhone;
}
public void setUserPhone(long userPhone) {
this.userPhone = userPhone;
}
public short getState() {
return state;
}
public void setState(short state) {
this.state = state;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state
+ ", createTime=" + createTime + "]";
}
public Seckill getSeckill() {
return seckill;
}
public void setSeckill(Seckill seckill) {
this.seckill = seckill;
}
}
3.在src/main/java中创建dao包,实现dao的接口
(1)SeckillDao.java
在这个接口中实现三个功能:减库存,根据seckillId查询秒杀对象,根据偏移量查询秒杀商品列表
package org.seckill.dao;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.seckill.entity.Seckill;
public interface SeckillDao {
/**
* 减库存
* @param seckillId
* @param killTime
* @return 如果影响行数>1,表示更新库存的记录行数
*/
int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime);
/**
* 根据seckillId 查询秒杀的对象
* @param seckillId
* @return
*/
Seckill queryById(long seckillId);
/**
* 根据偏移量查询秒杀商品列表
* @param offet
* @param limit
* @return
*/
List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
}
(2)SuccessKilledDao.java
在这个接口中实现两个功能:插入购买明细,根据秒杀商品的id查询明细SuccessKilled对象
package org.seckill.dao;
import org.apache.ibatis.annotations.Param;
import org.seckill.entity.SuccessKilled;
public interface SuccessKilledDao {
/**
* 插入购买明细,可过滤重复
* @param secKillId
* @param userPhone
* @return 插入的行数
*/
int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
/**
* 根据秒杀商品的id查询明细SuccessKilled对象(该对象携带了Seckill秒杀产品对象)
* @param seckillId
* @return
*/
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
}
4.基于Maybatis实现Dao
Mybatis映射数据库与entity的对象。
(1)sql写在哪儿?:mybatis提供两种方法:XML提供sql,注解提供sql。
本项目中使用Xml提供sql。
(2)如何实现DAO接口?mybatis提供两种方法:Mapper自动实现DAO接口,API编程方式实现DAO接口。
本项目中使用Mapper自动实现DAO接口。
5.创建一个mybatis全局文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置全局属性 -->
<settings>
<!-- 使用jdbc的getGeneratedKeys 获取数据库自增主键值 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 使用列别名替换列名 默认:true select name as title from table -->
<setting name="useColumnLabel" value="true" />
<!-- 开启驼峰命名转换:Table(create_time) -> entity(createTime) -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
6.创建mybatis映射文件
(1)SeckillDao.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.seckill.dao.SeckillDao">
<!--目的:为dao接口方法提供sql语句配置
即针对dao接口中的方法编写我们的sql语句-->
<update id="reduceNumber">
UPDATE seckill
SET number = number-1
WHERE seckill_id=#{seckillId}
AND start_time <![CDATA[ <= ]]> #{killTime}
AND end_time >= #{killTime}
AND number > 0;
</update>
<select id="queryById" resultType="Seckill" parameterType="long">
SELECT *
FROM seckill
WHERE seckill_id=#{seckillId}
</select>
<select id="queryAll" resultType="Seckill">
SELECT *
FROM seckill
ORDER BY create_time DESC
limit #{offset},#{limit}
</select>
</mapper>
(2)SuccessKilledDao.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.seckill.dao.SuccessKilledDao">
<insert id="insertSuccessKilled">
<!--当出现主键冲突时(即重复秒杀时),会报错;不想让程序报错,加入ignore-->
INSERT ignore INTO success_killed(seckill_id,user_phone,state)
VALUES (#{seckillId},#{userPhone},0)
</insert>
<select id="queryByIdWithSeckill" resultType="SuccessKilled">
<!--根据seckillId查询SuccessKilled对象,并携带Seckill对象-->
<!--如何告诉mybatis把结果映射到SuccessKill属性同时映射到Seckill属性-->
<!--可以*控制SQL语句-->
SELECT
sk.seckill_id,
sk.user_phone,
sk.create_time,
sk.state,
s.seckill_id "seckill.seckill_id",
s.name "seckill.name",
s.number "seckill.number",
s.start_time "seckill.start_time",
s.end_time "seckill.end_time",
s.create_time "seckill.create_time"
FROM success_killed sk
INNER JOIN seckill s ON sk.seckill_id=s.seckill_id
WHERE sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}
</select>
</mapper>
7.mybatis整合spring
整合目标:更少的编码,更少的配置,足够的灵活性。
创建spring-dao.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"
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">
<!-- 配置整合mybatis过程 -->
<!-- 1.配置数据库相关参数 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 2.配置数据库的连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<!-- 配置连接池属性 -->
<property name="driverClass" value="${jdbc.driverClass}" />
<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" />
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="1000" />
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2" />
</bean>
<!-- 约定大于配置 -->
<!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置mybatis全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 扫描entity包,使用别名 org.seckill.entity.Seckill -> seckill -->
<property name="typeAliasesPackage" value="org.seckill.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="org.seckill.dao" />
</bean>
</beans>
第二部分:Java高并发秒杀API之业务分析与Service层
DAO层工作演变为:接口设计+SQL编写
代码和SQL分离,方便review。
DAO拼接等逻辑在Service层完成。
1.在src/main/java中创建service包,包含service接口和实现类
创建seckillService接口:
此接口实现了四个功能:查询所有秒杀记录,查询单个秒杀记录,秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间,执行秒杀操作
package org.seckill.service;
import java.util.List;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
/**
* 业务接口:站在“使用者”的角度设计接口
* 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
*
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
*/
SeckillExecution executeSeckill(long seckillId,long userPhone,String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}
2.在src/main/java中创建exception包,包含service接口所需要的一些异常(重复秒杀,秒杀已关闭..)
(1)RepeatKillException.java 重复秒杀异常
package org.seckill.exception;
/**
* 重复秒杀异常(运行期异常)
* @author hasee
*
*/
public class RepeatKillException extends SeckillException {
private static final long serialVersionUID = 1457356743956329471L;
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
(2)SeckillCloseException.java 秒杀关闭异常
package org.seckill.exception;
/**
* 秒杀关闭异常
* @author hasee
*
*/
public class SeckillCloseException extends SeckillException {
private static final long serialVersionUID = -850110949024776554L;
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
(3)SeckillException 秒杀相关业务异常
package org.seckill.exception;
/**
* 秒杀相关业务异常
* @author hasee
*
*/
public class SeckillException extends RuntimeException {
private static final long serialVersionUID = -3848327706412041512L;
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}
3.在src/main/java中创建dto包,数据传输层,用来存放一些表示数据的一些类型,关注的是web和service的数据传递
(1)Exposer.java 暴露秒杀地址。
package org.seckill.dto;
/**
* 暴露秒杀地址DTO
* @author hasee
*
*/
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 +
'}';
}
}
(2)SeckillExecution.java
package org.seckill.dto;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
/**
* 封装秒杀执行后的结果
*
* @author hasee
*
*/
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.getStateInfo();
this.successKilled = successKilled;
}
//秒杀失败
public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {
this.seckillId = seckillId;
this.state = statEnum.getState();
this.stateInfo = statEnum.getStateInfo();
}
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 +
'}';
}
}
4.在src/main/java中创建service.Impl包,包含service接口的实现类
SeckillServiceImpl.java
package org.seckill.service.impl;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
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 org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.service.SeckillService;
@Service
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//注入Service依赖
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
// md5盐值字符串,用于混淆MD5
private final String slat = "fkdsjfnsdkfjsdfksbkf31ryq9ry83thwu%&$&^%";
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill = seckillDao.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
}
Date startTime = seckill.getStartTime();
Date endTime = seckill.getEndTime();
// 系统当前时间
Date nowTime = new Date();
if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {
return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
}
// 转化特定字符串的过程,不可逆
String md5 = getMD5(seckillId);
return new Exposer(true, md5, seckillId);
}
private String getMD5(long seckillId) {
String base = seckillId + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1:开发团队达成一致约定,明确标注事务方法的编程风格
* 2:保证事务方法的执行时间尽可能短,不要穿插其他的网络操作,RPC/HTTP请求或者剥离到事务方法外部
* 3:不是所有的方法都需要事务,如:只有一条修改操作,只读操作不需要事务控制
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
throw new SeckillException("Seckill data rewrite");
}
// 执行秒杀逻辑:减库存 + 记录购买行为
Date nowTime = new Date();
try {
// 减库存
int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
if (updateCount <= 0) {
// 没有更新到记录,秒杀结束
throw new SeckillCloseException("Seckill is Closed");
} else {
// 记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
// 唯一:seckillid,userphone
if (insertCount <= 0) {
// 重复秒杀
throw new RepeatKillException("seckill repeated");
} else {
// 秒杀成功
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
}
}
} catch(SeckillCloseException e1){
throw e1;
} catch(RepeatKillException e2){
throw e2;
} catch (Exception e) {
logger.error(e.getMessage(),e);
//所有编译器异常,转化为运行期异常
throw new SeckillException("seckill inner error:"+e.getMessage());
}
}
}
4.在src/main/java中创建enums包,开发数据字典
SeckillStatEnum.java
package org.seckill.enums;
/**
* 使用枚举表述常量数据字典
* @author hasee
*
*/
public enum SeckillStatEnum {
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATA_REWRITE(-3,"数据篡改");
private int state;
private String stateInfo;
SeckillStatEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static SeckillStatEnum stateOf(int index){
for(SeckillStatEnum state : values()){
if(state.getState() == index){
return state;
}
}
return null;
}
}
5.使用spring托管service,在spring包中创建spring-service.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">
<!-- 扫描service包下所有使用注解的类型 -->
<context:component-scan base-package="org.seckill.service"></context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库的连接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置基于注解的声明式事务
默认使用注解来管理事务行为
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
第三部分:Java高并发秒杀API之业务分析与web层
在这一部分中主要用到的:restful,Spirngmvc,bootstrap+jQuery
前端产品交互设计:
(1)前端页面流程:
(2)详情页逻辑流程分析
Restful接口设计:
SpringMVC:
流程:
1.整合配置SpirngMVC框架
在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>
2.在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: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-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 配置springMVC -->
<!-- 1.开启springMVC注解模式 -->
<!-- 简化配置:
1.自动注册DefaultAnnotationMapping,AnnotationMethodHandlerAdapter
2.提供一系列功能:数据绑定,数字和日期的format @NumberFormat,@DateTimeFormat,
xml,json的默认读写支持
-->
<mvc:annotation-driven></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相关的bean -->
<context:component-scan base-package="org.seckill.web"></context:component-scan>
</beans>
3.在src/main/java中创建web包,使用SpringMVC实现Restful接口
创建SeckillController.java
package org.seckill.web;
import java.util.Date;
import java.util.List;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.dto.SeckillResult;
import org.seckill.entity.Seckill;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.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.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/seckill") // url:模块/资源/{}/细分
public class SeckillController {
@Autowired
private SeckillService seckillService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model) {
// list.jsp+mode=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) {
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
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", 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());
}
}
4.基于bootstrap开发页面结构,在WEB/INF下新建jsp文件夹
(1)创建list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 引入jstl -->
<%@include file="commom/tag.jsp" %>
<!DOCTYPE html>
<html>
<head>
<title>秒杀列表页</title>
<%@include file="commom/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 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="${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>
(2)创建detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 引入jstl -->
<%@include file="commom/tag.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>秒杀详情页</title>
<%@include file="commom/head.jsp"%>
</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 class="panel-body"></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="userPhone" 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>
</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="https://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="${pageContext.request.contextPath}/resources/script/seckill.js"></script>
<script type="text/javascript">
$(function() {
//使用EL表达式传入参数
seckill.detail.init({
seckillId : "${seckill.seckillId}",
startTime : "${seckill.startTime.time}",
endTime : "${seckill.endTime.time}"
});
});
</script>
</html>
(3)在jsp文件夹下创建common文件夹,用于存放jsp页面中常用配置
(a)head.jsp
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- å¼å¥ Bootstrap -->
<link
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
<!-- HTML5 Shiv å 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]-->
(b)tag.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
5.通过jQuery的cookie插件实现交互登录
将jQuery的cookie插件和计时器插件写入detail.jsp中
<%--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="https://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>
6.在/src/main/webapp/resources/script/下创建seckill.js,将逻辑写入.js文件中
.js中包含手机号验证,执行秒杀操作,实现计时器的相关逻辑代码
//存放主要交互逻辑的js代码
// javascript 模块化(package.类.方法)
//seckill.detail.init(params);
var seckill = {
// 封装秒杀相关ajax的url
URL: {
now: function () {
return '/seckill/seckill/time/now';
},
exposer: function (seckillId) {
return '/seckill/seckill/' + seckillId + '/exposer';
},
execution: function (seckillId, md5) {
return '/seckill/seckill/' + seckillId + '/' + md5 + '/execution';
}
},
// 验证手机号
validatePhone : function(phone) {
if (phone && phone.length == 11 && !isNaN(phone)) {
return true;// 直接判断对象会看对象是否为空,空就是undefine就是false; isNaN 非数字返回true
} else {
return false;
}
},
handleSeckillkill : function(seckillId,node) {
// 获取秒杀地址,控制现实逻辑,执行秒杀
node.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');// 按钮
console.log('exposerUrl=' + seckill.URL.exposer(seckillId));//TODO
$.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);
// 使用one,帮定一次点击事件
$('#killBtn').one('click',function() {
// 执行秒杀请求的操作
// 1.先禁用按钮
$(this).addClass('disabled');
// 2.发送秒杀的请求执行秒杀
$.post(killUrl, {}, function(result){
console.log("result:" + result);
if (result && result['success']) {
var killResult = result['data'];
var state = result['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) {
var seckillBox = $('#seckill-box');
if (nowTime > endTime) {
// 秒杀结束
seckillBox.html('秒杀结束!');
} else if (nowTime < startTime) {
// 秒杀未开始,计时事件绑定
var killTime = new Date(startTime-0 + 1000);// todo 防止时间偏移
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.handleSeckillkill(seckillId, seckillBox);
}
},
// 详情页秒杀逻辑
detail : {
// 详情页初始化
init : function(params) {
// 手机验证和登录,计时交互
// 规划我们的交互流程
// 在cookie中查找手机号
var userPhone = $.cookie('userPhone');
// 验证手机号
if (!seckill.validatePhone(userPhone)) {
// 绑定手机 控制输出
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show : true,// 显示弹出层
backdrop : false,// 禁止位置关闭
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(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);
}
});
}
}
};
至此,项目相关的页面,逻辑代码已经完成。将maven项目发布到tomcat中,即可访问到页面。