欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SSM框架搭建秒杀项目

程序员文章站 2024-01-15 22:26:46
...

SSM框架搭建秒杀项目

此项目为慕课网上yijun zhang老师的课程。

项目展示:

  (1)秒杀列表页:

SSM框架搭建秒杀项目

  (2)秒杀详情页(输入手机号,验证成功,才可执行秒杀)

SSM框架搭建秒杀项目

  (3)秒杀详情页(执行秒杀)

SSM框架搭建秒杀项目

  (4)秒杀详情页(秒杀成功)

SSM框架搭建秒杀项目

(5)秒杀详情页(重复秒杀)

SSM框架搭建秒杀项目

(6)秒杀详情页(秒杀倒计时)

SSM框架搭建秒杀项目

(7)秒杀详情页(秒杀结束)

SSM框架搭建秒杀项目

课程分为四个部分:

Java高并发秒杀API之业务分析与DAO层

点击打开链接

Java高并发秒杀API之Service层

点击打开链接

Java高并发秒杀API之web层

点击打开链接

Java高并发秒杀API之高并发优化

点击打开链接

本项目使用到的开发工具:eclipse,Navicat,tomcat

分析秒杀业务:

SSM框架搭建秒杀项目

秒杀业务的核心为:对库存的处理。

用户针对库存业务的分析:

SSM框架搭建秒杀项目

购买行为:谁购买成功了,什么时间购买,付款/发货信息

MySQL实现秒杀难点分析:高并发主要发生在多个用户对一个产品的秒杀,产生一种竞争,这个竞争反映到MySQL背后的技术为事务+行级锁。

事务:Start Transaction ,update库存数量,insert购买明细,Commit(红色字体为主要竞争)

行级锁:

SSM框架搭建秒杀项目

秒杀的难点为:如何高效的处理竞争。

实现秒杀功能:秒杀接口暴露,实行秒杀,相关查询。

使用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)前端页面流程:

SSM框架搭建秒杀项目

(2)详情页逻辑流程分析

SSM框架搭建秒杀项目


Restful接口设计:

SSM框架搭建秒杀项目

SpringMVC:

SSM框架搭建秒杀项目

流程:

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中,即可访问到页面。


相关标签: java ssm