<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>seckill Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>4.3.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<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.8.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
<build>
<finalName>seckill</finalName>
</build>
</project>
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元秒杀iPhone 6s',100,'2017-01-01 00:00:00','2017-01-02 00:00:00'),
('500元秒杀iPad3',200,'2017-01-01 00:00:00','2017-01-02 00:00:00'),
('300元秒杀小米5',300,'2017-01-01 00:00:00','2017-01-02 00:00:00'),
('100元秒杀红米note3',500,'2017-01-01 00:00:00','2017-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:已付款',
create_time timestamp NOT NULL COMMENT '创建时间',
PRIMARY KEY(seckill_id,user_phone),/*联合主键*/
key idx_create_time(create_time)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'
--连接数据库控制台
mysql start
mysql -u root -p
--为什么手写DDL
--记录每次上线的DDL修改
--上线V1.0
package org.seckill.entity;
import java.util.Date;
public class Seckill {
private long seckillId;
private String seckillName;
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 getSeckillName() {
return seckillName;
}
public void setSeckillName(String seckillName) {
this.seckillName = seckillName;
}
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 + ", seckillName="
+ seckillName + ", number=" + number + ", startTime="
+ startTime + ", endTime=" + endTime + ", createTime="
+ createTime + "]";
}
}
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;
}
public Seckill getSeckill() {
return seckill;
}
public void setSeckill(Seckill seckill) {
this.seckill = seckill;
}
@Override
public String toString() {
return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state
+ ", createTime=" + createTime + ", seckill=" + seckill + "]";
}
}
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
*/
public int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime);
/**
* 根据id查询秒杀对象
* @param seckillId
* @return
*/
public Seckill queryById(long seckillId);
/**
* 根据偏移量查询秒杀商品列表
* @param offset
* @param limit
* @return
*/
public List<Seckill> queryAll(@Param("offset") int offset ,@Param("limit") int limit);
/**
* 使用存储过程执行秒杀
* @param param
*/
public void killProcedure(Map<String,Object> param);
}
package org.seckill.dao;
import org.apache.ibatis.annotations.Param;
import org.seckill.entity.SuccessKilled;
public interface SuccessKilledDAO {
/**
* 插入购买明细,可过滤重复
* @param seckillId
* @param userPhone
* @return
*/
public int insertSuccessKilled(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
/**
* 根据id查询SuccessKilled并携带秒杀产品对象实体
* @param seckillId
* @return
*/
public SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
}
SeckillDao.xml
--------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.seckill.dao.SeckillDao">
<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" parameterType="long" resultType="Seckill">
select seckill_id,seckill_name,number,start_time,end_time,create_time
from seckill
where seckill_id = #{seckillId}
</select>
<select id="queryAll" resultType="Seckill">
select seckill_id,seckill_name,number,start_time,end_time,create_time
from seckill
order by create_time desc
limit #{offset},#{limit}
</select>
<select id="killProcedure" statementType="CALLABLE">
call execute_seckill(
#{seckillId,jdbcType=BIGINT,mode=IN},
#{phone,jdbcType=BIGINT,mode=IN},
#{killTime,jdbcType=TIMESTAMP,mode=IN},
#{result,jdbcType=INTEGER,mode=OUT}
)
</select>
</mapper>
SuccessKilledDAO.xml
--------------------
<?xml version="1.0" encoding="UTF-8"?>
<!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">
insert ignore into success_killed(seckill_id,user_phone,state)
values(#{seckillId},#{userPhone},0)
</insert>
<select id="queryByIdWithSeckill" resultType="SuccessKilled">
select
sk.seckill_id,
sk.user_phone,
sk.state,
sk.create_time,
s.seckill_id "seckill.seckill_id",
s.seckill_name "seckill.seckill_name",
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>
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>
<setting name="useGeneratedKeys" value="true"/>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<property name="autoCommitOnClose" value="false"/>
<property name="checkoutTimeout" value="5000"/>
<property name="acquireRetryAttempts" value="2"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="org.seckill.entity"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="org.seckill.dao"/>
</bean>
</beans>
package org.seckill.dao
import java.util.Date
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.seckill.entity.Seckill
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {
@Autowired
private SeckillDao seckillDao
@Test
public void testReduceNumber() {
Date killTime = new Date()
int updateCount = seckillDao.reduceNumber(1000L, killTime)
System.out.println(updateCount)
}
@Test
public void testQueryById() {
long id = 1000
Seckill seckill = seckillDao.queryById(id)
System.out.println(seckill.getSeckillName())
System.out.println(seckill)
}
@Test
public void testQueryAll() {
List<Seckill> seckills = seckillDao.queryAll(0, 100)
for (Seckill seckill : seckills) {
System.out.println(seckill)
}
}
}
package org.seckill.dao
import org.junit.Test
import org.junit.runner.RunWith
import org.seckill.entity.SuccessKilled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SuccessKilledDAOTest {
@Autowired
private SuccessKilledDAO successKilledDAO
@Test
public void testInsertSuccessKilled() {
long seckillId = 1002L
long userPhone = 18792510201L
int insertCount = successKilledDAO.insertSuccessKilled(seckillId, userPhone)
System.out.println(insertCount)
}
@Test
public void testQueryByIdWithSeckill() {
long seckillId = 1002L
long userPhone = 18792510201L
SuccessKilled successKilled = successKilledDAO.queryByIdWithSeckill(seckillId, userPhone)
System.out.println(successKilled)
System.out.println(successKilled.getSeckill())
}
}
package org.seckill.service;
import java.util.List;
import org.seckill.dta.Exposer;
import org.seckill.dta.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
*/
public List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
public Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间
* @param seckillId
*/
public Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
*/
public SeckillExecution executeSeckill(long seckillId,long userPhone,String md5)throws SeckillException,RepeatKillException,SeckillCloseException;
}
package org.seckill.service.impl;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDAO;
import org.seckill.dta.Exposer;
import org.seckill.dta.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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
@Service
public class SeckillServiceImpl implements SeckillService {
private Log logger = LogFactory.getLog(this.getClass());
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDAO successKilledDAO;
private final String slat = "sfdfbghghj^*((#($#$#$$Q#WEsdsd";
@Override
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}
@Override
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
@Override
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);
}
@Transactional
@Override
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 killTime = new Date();
try {
int updateCount = seckillDao.reduceNumber(seckillId, killTime);
if(updateCount <= 0){
throw new SeckillCloseException("seckill is closed");
}else{
int insertCount = successKilledDAO.insertSuccessKilled(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.getLocalizedMessage());
}
}
private String getMD5(long seckillId){
String base = seckillId + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
};
@Override
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5){
if (md5 == null || !md5.equals(getMD5(seckillId))) {
return new SeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);
}
Date killTime = new Date();
Map<String,Object> map = new HashMap<String,Object>();
map.put("seckillId", seckillId);
map.put("phone", userPhone);
map.put("killTime", killTime);
map.put("result", null);
try {
seckillDao.killProcedure(map);
int result = MapUtils.getInteger(map,"result",-2);
if(result == 1){
SuccessKilled successKilled = successKilledDAO.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);
}else{
return new SeckillExecution(seckillId,SeckillStatEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(),e);
return new SeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);
}
}
}
package org.seckill.dta;
/**
* 暴露秒杀地址DTO
* @author Samsung
*
*/
public class Exposer {
private boolean exposed;
/**
* 一种加密措施
*/
private String md5;
/**
* id
*/
private long seckilId;
/**
* 系统当前时间(毫秒)
*/
private long now;
/**
* 开启时间
*/
private long start;
/**
* 结束时间
*/
private long end;
public Exposer(boolean exposed, String md5, long seckilId) {
super();
this.exposed = exposed;
this.md5 = md5;
this.seckilId = seckilId;
}
public Exposer(boolean exposed, long seckilId,long now, long start, long end) {
super();
this.exposed = exposed;
this.seckilId = seckilId;
this.now = now;
this.start = start;
this.end = end;
}
public Exposer(boolean exposed, long seckilId) {
super();
this.exposed = exposed;
this.seckilId = seckilId;
}
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 getSeckilId() {
return seckilId;
}
public void setSeckilId(long seckilId) {
this.seckilId = seckilId;
}
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 + ", seckilId=" + seckilId + ", now=" + now + ", start="
+ start + ", end=" + end + "]";
}
}
package org.seckill.dta;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
/**
* 封装秒杀执行后结果
*
*/
public class SeckillExecution {
private long seckillId;
/**
* 秒杀执行结果状态
*/
private int state;
/**
* 状态标示
*/
private String stateInfo;
/**
* 秒杀成功对象
*/
private SuccessKilled successKilled;
public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {
super();
this.seckillId = seckillId;
this.state = statEnum.getState();
this.stateInfo = statEnum.getStateInfo();
this.successKilled = successKilled;
}
public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {
super();
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 + "]";
}
}
package org.seckill.enums;
/**
*使用枚举表述常量数据字段
*/
public enum SeckillStatEnum {
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATA_REWRITE(-3,"数据篡改");
private int state;
private String stateInfo;
private SeckillStatEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
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;
}
}
package org.seckill.exception;
/**
* 重复秒杀异常(运行时异常)
*
*/
public class RepeatKillException extends SeckillException{
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
package org.seckill.exception;
public class SeckillCloseException extends SeckillException{
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
package org.seckill.exception;
public class SeckillException extends RuntimeException{
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
public SeckillException(String message) {
super(message);
}
}
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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="org.seckill.service"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
package org.seckill.service
import static org.junit.Assert.*
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.seckill.dta.Exposer
import org.seckill.dta.SeckillExecution
import org.seckill.entity.Seckill
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml","classpath:spring/spring-service.xml"})
public class SeckillServiceTest {
@Autowired
private SeckillService seckillService
private Log logger = LogFactory.getLog(this.getClass())
@Test
public void testGetSeckillList() {
//Closing non transactional SqlSession
List<Seckill> list = seckillService.getSeckillList()
logger.info(list)
}
@Test
public void testGetById() {
long id =1000
Seckill seckill = seckillService.getById(id)
logger.info(seckill)
}
@Test
public void testExportSeckillUrl() {
long id =1000
Exposer exposer = seckillService.exportSeckillUrl(id)
logger.info(exposer)
}
@Test
public void testExecuteSeckill() {
//Transaction synchronization committing SqlSession
//org.seckill.exception.RepeatKillException: seckill repeated
long id =1000
long phone = 18790410200L
String md5 = "a035deb4820beb0ec3a880c07d785a75"
SeckillExecution seckillExecution = seckillService.executeSeckill(id, phone, md5)
logger.info(seckillExecution)
}
}
spring-web.xml
--------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 开启SpringMVC注解模式
1.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
2.提供一系列支持:数据绑定、日期和数字format、jason和xml读写支持
-->
<mvc:annotation-driven/>
<!-- 静态资源默认servlet配置
1.加入对静态资源的处理:js、gif、png
2.允许使用"/"坐整体映射
-->
<mvc:default-servlet-handler/>
<!-- 配置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/" />
<property name="suffix" value=".jsp" />
</bean>
<context:component-scan base-package="org.seckill.web"/>
</beans>
package org.seckill.web;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seckill.dta.Exposer;
import org.seckill.dta.SeckillExecution;
import org.seckill.dta.SeckillResult;
import org.seckill.entity.Seckill;
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;
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")
public class SeckillController {
@Autowired
private SeckillService seckillService;
private Log logger = LogFactory.getLog(this.getClass());
@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 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";
}
@RequestMapping(value = "/{seckillId}/exposer",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId){
SeckillResult<Exposer> result;
try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
result = new SeckillResult<Exposer>(true, exposer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
result = new SeckillResult<Exposer>(false, e.getMessage());
}
return result;
}
@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="killPhone",required=false) Long phone){
if(phone == null){
return new SeckillResult<SeckillExecution>(false, "未注册");
}
try {
SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, phone, md5);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
} catch (RepeatKillException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
} catch (SeckillCloseException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
} catch (SeckillException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
}
}
@RequestMapping(value = "/time/now",method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Long> time(){
Date now = new Date();
return new SeckillResult<Long>(true,now.getTime());
}
}
package org.seckill.dta;
public class SeckillResult<T> {
private boolean success;
private T data;
private String error;
public SeckillResult(boolean success, T data) {
super();
this.success = success;
this.data = data;
}
public SeckillResult(boolean success, String error) {
super();
this.success = success;
this.error = error;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
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">
<servlet>
<servlet-name>seckill-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<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>
head.jsp
--------
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<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>
tag.jsp
-------
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
detail.jsp
----------
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!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="panel-heading">
<h1>${seckill.seckillName}</h1>
</div>
<div class="panel-body">
<h2 class="text-danger">
<span class="glyphicon glyphicon-time"></span>
<span class="glyphicon" id="seckill-box"></span>
</h2>
</div>
</div>
</div>
<div id="killPhoneModal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title text-center">
<span class="glyphicon glyphicon-phone"></span>秒杀电话:
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" name="killPhone" id="killPhoneKey"
placeholder="填手机号^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>
<script
src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<script
src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script
src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script
src="http://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/seckill.js"></script>
<script type="text/javascript">
$(function(){
seckill.detail.init({
seckillId:"${seckill.seckillId}",
startTime:"${seckill.startTime.time}",
endTime:"${seckill.endTime.time}"
})
})
</script>
</html>
list.jsp
--------
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@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 var="seckill" items="${list}">
<tr>
<td>${seckill.seckillName}</td>
<td>${seckill.number}</td>
<td><fmt:formatDate value="${seckill.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><fmt:formatDate value="${seckill.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><fmt:formatDate value="${seckill.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td><a class="btn btn-info" href="/seckill/${seckill.seckillId}/detail" target="blank">link</a></td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
<script
src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<script
src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>
jdbc.properties
---------------
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&useSSL=true&charaterEncoding=utf8
jdbc.username=root
jdbc.password=root
log4j.properties
----------------
log4j.rootLogger=ERROR,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.seckill=TRACE