1. 程式人生 > >Java 實現高併發秒殺

Java 實現高併發秒殺

1 需求分析和技術難點:

(1) 分析:

     秒殺的時候:減少庫存和購買記錄明細兩個事件保持在同一個事物中。

     使用聯合查詢避免同一使用者多次秒殺同一商品(利用在插入購物明細表中的秒殺id和使用者的唯一標識來避免)。

(2) 秒殺難點:事務和行級鎖的處理

(3) 實現那些秒殺系統(以天貓的秒殺系統為例)

(4) 我們如何實現秒殺功能?

     ① 秒殺介面暴漏

     ② 執行秒殺

     ③ 相關查詢

     下面我們以主要程式碼實現秒殺系統:

2.資料庫設計和DAO層

(1) 資料庫設計

  1. -- 資料庫初始化指令碼

  2. -- 建立資料庫

  3. CREATE DATABASE seckill;

  4. -- 使用資料庫

  5. use seckill;

  6. CREATE TABLE seckill(

  7. `seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品庫存ID',

  8. `name` VARCHAR(120) NOT NULL COMMENT '商品名稱',

  9. `number` int NOT NULL COMMENT '庫存數量',

  10. `start_time` TIMESTAMP NOT NULL COMMENT '秒殺開始時間',

  11. `end_time` TIMESTAMP NOT NULL COMMENT '秒殺結束時間',

  12. `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',

  13. PRIMARY KEY (seckill_id),

  14. key idx_start_time(start_time),

  15. key idx_end_time(end_time),

  16. key idx_create_time(create_time)

  17. )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒殺庫存表';

  18. -- 初始化資料

  19. INSERT into seckill(name,number,start_time,end_time)

  20. VALUES

  21. ('1000元秒殺iphone6',100,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  22. ('800元秒殺ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  23. ('6600元秒殺mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  24. ('7000元秒殺iMac',400,'2016-01-01 00:00:00','2016-01-02 00:00:00');

  25. -- 秒殺成功明細表

  26. -- 使用者登入認證相關資訊(簡化為手機號)

  27. CREATE TABLE success_killed(

  28. `seckill_id` BIGINT NOT NULL COMMENT '秒殺商品ID',

  29. `user_phone` BIGINT NOT NULL COMMENT '使用者手機號',

  30. `state` TINYINT NOT NULL DEFAULT -1 COMMENT '狀態標識:-1:無效 0:成功 1:已付款 2:已發貨',

  31. `create_time` TIMESTAMP NOT NULL COMMENT '建立時間',

  32. PRIMARY KEY(seckill_id,user_phone),/*聯合主鍵*/

  33. KEY idx_create_time(create_time)

  34. )ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒殺成功明細表';

  35. -- SHOW CREATE TABLE seckill;#顯示錶的建立資訊

(2) Dao層和對應的實體

① Seckill.java

  1. package com.force4us.entity;

  2. import org.springframework.stereotype.Component;

  3. import java.util.Date;

  4. public class Seckill {

  5. private long seckillId;

  6. private String name;

  7. private int number;

  8. private Date startTime;

  9. private Date endTime;

  10. private Date createTime;

  11. public long getSeckillId() {

  12. return seckillId;

  13. }

  14. public void setSeckillId(long seckillId) {

  15. this.seckillId = seckillId;

  16. }

  17. public String getName() {

  18. return name;

  19. }

  20. public void setName(String name) {

  21. this.name = name;

  22. }

  23. public int getNumber() {

  24. return number;

  25. }

  26. public void setNumber(int number) {

  27. this.number = number;

  28. }

  29. public Date getStartTime() {

  30. return startTime;

  31. }

  32. public void setStartTime(Date startTime) {

  33. this.startTime = startTime;

  34. }

  35. public Date getEndTime() {

  36. return endTime;

  37. }

  38. public void setEndTime(Date endTime) {

  39. this.endTime = endTime;

  40. }

  41. public Date getCreateTime() {

  42. return createTime;

  43. }

  44. public void setCreateTime(Date createTime) {

  45. this.createTime = createTime;

  46. }

  47. @Override

  48. public String toString() {

  49. return "Seckill{" +

  50. "seckillId=" + seckillId +

  51. ", name='" + name + '\'' +

  52. ", number=" + number +

  53. ", startTime=" + startTime +

  54. ", endTime=" + endTime +

  55. ", createTime=" + createTime +

  56. '}';

  57. }

  58. }

②  SuccessKilled.java

  1. package com.force4us.entity;

  2. import org.springframework.stereotype.Component;

  3. import java.util.Date;

  4. public class SuccessKilled {

  5. private long seckillId;

  6. private long userPhone;

  7. private short state;

  8. private Date createTime;

  9. private Seckill seckill;

  10. public long getSeckillId() {

  11. return seckillId;

  12. }

  13. public void setSeckillId(long seckillId) {

  14. this.seckillId = seckillId;

  15. }

  16. public long getUserPhone() {

  17. return userPhone;

  18. }

  19. public void setUserPhone(long userPhone) {

  20. this.userPhone = userPhone;

  21. }

  22. public short getState() {

  23. return state;

  24. }

  25. public void setState(short state) {

  26. this.state = state;

  27. }

  28. public Date getCreateTime() {

  29. return createTime;

  30. }

  31. public void setCreateTime(Date createTime) {

  32. this.createTime = createTime;

  33. }

  34. public Seckill getSeckill() {

  35. return seckill;

  36. }

  37. public void setSeckill(Seckill seckill) {

  38. this.seckill = seckill;

  39. }

  40. @Override

  41. public String toString() {

  42. return "SuccessKilled{" +

  43. "seckillId=" + seckillId +

  44. ", userPhone=" + userPhone +

  45. ", state=" + state +

  46. ", createTime=" + createTime +

  47. ", seckill=" + seckill +

  48. '}';

  49. }

  50. }

③  SeckillDao

  1. package com.force4us.dao;

  2. import com.force4us.entity.Seckill;

  3. import org.apache.ibatis.annotations.Param;

  4. import java.util.Date;

  5. import java.util.List;

  6. import java.util.Map;

  7. public interface SeckillDao {

  8. /**

  9. * 減庫存

  10. * @param seckillId

  11. * @param killTime

  12. * @return 如果影響行數>1,表示更新庫存的記錄行數

  13. */

  14. int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

  15. /**

  16. * 根據id查詢秒殺的商品資訊

  17. * @param seckillId

  18. * @return

  19. */

  20. Seckill queryById(@Param("seckillId") long seckillId);

  21. /**

  22. * 根據偏移量查詢秒殺商品列表

  23. * @param offset

  24. * @param limit

  25. * @return

  26. */

  27. List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);

  28. void killByProcedure(Map<String,Object> paramMap);

  29. }

④  SuccessKilledDao

  1. package com.force4us.dao;

  2. import com.force4us.entity.SuccessKilled;

  3. import org.apache.ibatis.annotations.Param;

  4. public interface SuccessKilledDao {

  5. /**

  6. * 插入購買明細,可過濾重複

  7. * @param seckillId

  8. * @param userPhone

  9. * @return 插入的行數

  10. */

  11. int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);

  12. /**

  13. * 根據秒殺商品ID查詢明細SuccessKilled物件, 攜帶了Seckill秒殺產品物件

  14. * @param seckillId

  15. * @param userPhone

  16. * @return

  17. */

  18. SuccessKilled queryByIdWithSeckill(@Param("seckillId") long , @Param("userPhone") long userPhone);

  19. }

⑤ mybatis配置檔案:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <!DOCTYPE configuration

  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">

  5. <configuration>

  6. <!-- 配置全域性屬性 -->

  7. <settings>

  8. <!-- 使用jdbc的getGeneratekeys獲取自增主鍵值 -->

  9. <setting name="useGeneratedKeys" value="true"/>

  10. <!--使用列別名替換列名  預設值為true

  11. select name as title(實體中的屬性名是title) form table;

  12. 開啟後mybatis會自動幫我們把表中name的值賦到對應實體的title屬性中

  13. -->

  14. <setting name="useColumnLabel" value="true"/>

  15. <!--開啟駝峰命名轉換Table:create_time到 Entity(createTime)-->

  16. <setting name="mapUnderscoreToCamelCase" value="true"/>

  17. </settings>

  18. </configuration>

⑥ SeckillDao.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <!DOCTYPE mapper

  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

  5. <mapper namespace="com.force4us.dao.SeckillDao">

  6. <update id="reduceNumber">

  7. UPDATE seckill

  8. SET number = number - 1

  9. WHERE seckill_id = #{seckillId}

  10. AND start_time <![CDATA[ <= ]]> #{killTime}

  11. AND end_time >= #{killTime}

  12. AND number > 0

  13. </update>

  14. <select id="queryById" resultType="Seckill" parameterType="long">

  15. SELECT *

  16. FROM seckill

  17. WHERE seckill_id = #{seckillId}

  18. </select>

  19. <select id="queryAll" resultType="Seckill">

  20. SELECT *

  21. FROM seckill

  22. ORDER BY create_time DESC

  23. limit #{offset},#{limit}

  24. </select>

  25. <select id="killByProcedure" statementType="CALLABLE">

  26. CALL excuteSeckill(

  27. #{seckillId, jdbcType=BIGINT, mode=IN},

  28. #{phone, jdbcType=BIGINT, mode=IN},

  29. #{killTime, jdbcType=TIMESTAMP, mode=IN},

  30. #{result, jdbcType=INTEGER, mode=OUT}

  31. )

  32. </select>

  33. </mapper>

⑦ SuccessKilledDao.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <!DOCTYPE mapper

  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

  5. <mapper namespace="com.force4us.dao.SuccessKilledDao">

  6. <insert id="insertSuccessKilled">

  7. <!--當出現主鍵衝突時(即重複秒殺時),會報錯;不想讓程式報錯,加入ignore-->

  8. INSERT ignore INTO success_killed(seckill_id,user_phone,state)

  9. VALUES (#{seckillId},#{userPhone},0)

  10. </insert>

  11. <select id="queryByIdWithSeckill" resultType="SuccessKilled">

  12. <!--根據seckillId查詢SuccessKilled物件,並攜帶Seckill物件-->

  13. <!--如何告訴mybatis把結果對映到SuccessKill屬性同時對映到Seckill屬性-->

  14. <!--可以自由控制SQL語句-->

  15. SELECT

  16. sk.seckill_id,

  17. sk.user_phone,

  18. sk.create_time,

  19. sk.state,

  20. s.seckill_id "seckill.seckill_id",

  21. s.name "seckill.name",

  22. s.number "seckill.number",

  23. s.start_time "seckill.start_time",

  24. s.end_time "seckill.end_time",

  25. s.create_time "seckill.create_time"

  26. FROM success_killed sk

  27. INNER JOIN seckill s ON sk.seckill_id = s.seckill_id

  28. WHERE sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}

  29. </select>

  30. </mapper>

⑧ Mybatis整合Service:spring-dao.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4. xmlns:contex="http://www.springframework.org/schema/context"

  5. 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">

  6. <!-- 配置整合mybatis過程-->

  7. <!-- 1、配置資料庫相關引數-->

  8. <contex:property-placeholder location="classpath:jdbc.properties"/>

  9. <!-- 2、配置資料庫連線池-->

  10. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

  11. <!-- 配置連結屬性-->

  12. <property name="driverClass" value="${jdbc.driver}"/>

  13. <property name="user" value="${jdbc.username}"/>

  14. <property name="password" value="${jdbc.password}"/>

  15. <property name="jdbcUrl" value="${jdbc.url}"/>

  16. <!-- 配置c3p0私有屬性-->

  17. <property name="maxPoolSize" value="30"/>

  18. <property name="minPoolSize" value="10"/>

  19. <!--關閉連線後不自動commit-->

  20. <property name="autoCommitOnClose" value="false"/>

  21. <!--獲取連線超時時間-->

  22. <property name="checkoutTimeout" value="1000"/>

  23. <!--當獲取連線失敗重試次數-->

  24. <property name="acquireRetryAttempts" value="2"/>

  25. </bean>

  26. <!-- 3、配置sqlSessionFactory物件-->

  27. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

  28. <!--注入資料庫連線池-->

  29. <property name="dataSource" ref="dataSource"/>

  30. <!-- 配置mybatis全域性配置檔案:mybatis-config.xml-->

  31. <property name="configLocation" value="classpath:mybatis-config.xml"/>

  32. <!-- 掃描entity包,使用別名,多個用;隔開-->

  33. <property name="typeAliasesPackage" value="com.force4us.entity"/>

  34. <!-- 掃描sql配置檔案:mapper需要的xml檔案-->

  35. <property name="mapperLocations" value="classpath:mapper/*.xml"/>

  36. </bean>

  37. <!-- 4.配置掃描Dao介面包,動態實現Dao介面,注入到spring容器-->

  38. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  39. <!-- 注入sqlSessionFactory-->

  40. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

  41. <!--給出需要掃描的Dao介面-->

  42. <property name="basePackage" value="com.force4us.dao"/>

  43. </bean>

  44. <!--redisDao-->

  45. <bean id="redisDao" class="com.force4us.dao.cache.RedisDao">

  46. <constructor-arg index="0" value="localhost"/>

  47. <constructor-arg index="1" value="6379"/>

  48. </bean>

  49. </beans>

3 Service層

① SeckillService

  1. package com.force4us.service;

  2. import com.force4us.dto.Exposer;

  3. import com.force4us.dto.SeckillExecution;

  4. import com.force4us.entity.Seckill;

  5. import com.force4us.exception.RepeatKillException;

  6. import com.force4us.exception.SeckillCloseException;

  7. import com.force4us.exception.SeckillException;

  8. import java.util.List;

  9. /**業務介面:站在使用者(程式設計師)的角度設計介面

  10. * 三個方面:1.方法定義粒度,方法定義的要非常清楚2.引數,要越簡練越好

  11. * 3.返回型別(return 型別一定要友好/或者return異常,我們允許的異常)

  12. */

  13. public interface SeckillService {

  14. /**

  15. * 查詢全部秒殺記錄

  16. * @return

  17. */

  18. List<Seckill> getSeckillList();

  19. /**

  20. * 查詢單個秒殺記錄

  21. * @param seckillId

  22. * @return

  23. */

  24. Seckill getById(long seckillId);

  25. /**

  26. * 在秒殺開啟時輸出秒殺介面的地址,否則輸出系統時間和秒殺時間

  27. */

  28. Exposer exportSeckillUrl(long seckillId);

  29. /**

  30. * 執行秒殺操作,有可能失敗,有可能成功,所以要丟擲我們允許的異常

  31. * @param seckillId

  32. * @param userPhone

  33. * @param md5

  34. * @return

  35. * @throws SeckillException

  36. * @throws RepeatKillException

  37. * @throws SeckillCloseException

  38. */

  39. SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)

  40. throws SeckillException, RepeatKillException, SeckillCloseException;

  41. SeckillExecution executeSeckillProcedure(long seckillId,long userPhone,String md5)

  42. throws SeckillException,RepeatKillException,SeckillCloseException;

  43. }

② SeckillServiceImpl

  1. package com.force4us.service.impl;

  2. import com.force4us.dao.SeckillDao;

  3. import com.force4us.dao.SuccessKilledDao;

  4. import com.force4us.dao.cache.RedisDao;

  5. import com.force4us.dto.Exposer;

  6. import com.force4us.dto.SeckillExecution;

  7. import com.force4us.entity.Seckill;

  8. import com.force4us.entity.SuccessKilled;

  9. import com.force4us.enums.SeckillStatEnum;

  10. import com.force4us.exception.RepeatKillException;

  11. import com.force4us.exception.SeckillCloseException;

  12. import com.force4us.exception.SeckillException;

  13. import com.force4us.service.SeckillService;

  14. import org.apache.commons.collections4.MapUtils;

  15. import org.slf4j.Logger;

  16. import org.slf4j.LoggerFactory;

  17. import org.springframework.beans.factory.annotation.Autowired;

  18. import org.springframework.stereotype.Service;

  19. import org.springframework.transaction.annotation.Transactional;

  20. import org.springframework.util.DigestUtils;

  21. import javax.annotation.Resource;

  22. import java.util.Date;

  23. import java.util.HashMap;

  24. import java.util.List;

  25. import java.util.Map;

  26. @Service

  27. public class SeckillServiceImpl implements SeckillService {

  28. //日誌物件

  29. private Logger logger = LoggerFactory.getLogger(this.getClass());

  30. @Autowired

  31. private SeckillDao seckillDao;

  32. @Autowired

  33. private SuccessKilledDao successKilledDao;

  34. @Autowired

  35. private RedisDao redisDao;

  36. //加入一個混淆字串(秒殺介面)的salt,為了我避免使用者猜出我們的md5值,值任意給,越複雜越好

  37. private final String salt = "sadjgioqwelrhaljflutoiu293480523*&%*&*#";

  38. public List<Seckill> getSeckillList() {

  39. return seckillDao.queryAll(0, 4);

  40. }

  41. public Seckill getById(long seckillId) {

  42. return seckillDao.queryById(seckillId);

  43. }

  44. public Exposer exportSeckillUrl(long seckillId) {

  45. //快取優化

  46. //1。訪問redi

  47. Seckill seckill = redisDao.getSeckill(seckillId);

  48. if (seckill == null) {

  49. //2.訪問資料庫

  50. seckill = seckillDao.queryById(seckillId);

  51. if (seckill == null) {//說明查不到這個秒殺產品的記錄

  52. return new Exposer(false, seckillId);

  53. } else {

  54. //3,放入redis

  55. redisDao.putSeckill(seckill);

  56. }

  57. }

  58. Date startTime = seckill.getStartTime();

  59. Date endTime = seckill.getEndTime();

  60. Date nowTime = new Date();

  61. //若是秒殺未開啟

  62. if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {

  63. return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());

  64. }

  65. //秒殺開啟,返回秒殺商品的id、用給介面加密的md5

  66. String md5 = getMD5(seckillId);

  67. return new Exposer(true, md5, seckillId);

  68. }

  69. private String getMD5(long seckillId) {

  70. String base = seckillId + "/" + salt;

  71. String md5 = DigestUtils.md5DigestAsHex(base.getBytes());

  72. return md5;

  73. }

  74. @Transactional

  75. /**

  76. * 使用註解控制事務方法的優點:

  77. * 1.開發團隊達成一致約定,明確標註事務方法的程式設計風格

  78. * 2.保證事務方法的執行時間儘可能短,不要穿插其他網路操作RPC/HTTP請求或者剝離到事務方法外部

  79. * 3.不是所有的方法都需要事務,如只有一條修改操作、只讀操作不要事務控制

  80. */

  81. public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {

  82. if (md5 == null || !md5.equals(getMD5(seckillId))) {

  83. throw new SeckillException("seckill data rewrite");

  84. }

  85. //執行秒殺邏輯:減庫存+記錄購買行為

  86. Date nowTime = new Date();

  87. try {

  88. //否則更新了庫存,秒殺成功,增加明細

  89. int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);

  90. //看是否該明細被重複插入,即使用者是否重複秒殺

  91. if (insertCount <= 0) {

  92. throw new RepeatKillException("seckill repeated");

  93. } else {

  94. //減庫存,熱點商品競爭,update方法會拿到行級鎖

  95. int updateCount = seckillDao.reduceNumber(seckillId, nowTime);

  96. if (updateCount <= 0) {

  97. //沒有更新庫存記錄,說明秒殺結束 rollback

  98. throw new SeckillCloseException("seckill is closed");

  99. } else {

  100. //秒殺成功,得到成功插入的明細記錄,並返回成功秒殺的資訊 commit

  101. SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);

  102. return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);

  103. }

  104. }

  105. } catch (SeckillCloseException e1) {

  106. throw e1;

  107. } catch (RepeatKillException e2) {

  108. throw e2;

  109. } catch (Exception e) {

  110. logger.error(e.getMessage(), e);

  111. //所有編譯器異常,轉化成執行期異常

  112. throw new SeckillException("seckill inner error:" + e.getMessage());

  113. }

  114. }

  115. public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {

  116. if (md5 == null || !md5.equals(getMD5(seckillId))) {

  117. return new SeckillExecution(seckillId, SeckillStatEnum.DATE_REWRITE);

  118. }

  119. Date time = new Date();

  120. Map<String, Object> map = new HashMap<String, Object>();

  121. map.put("seckillId", seckillId);

  122. map.put("phone", userPhone);

  123. map.put("killTime", time);

  124. map.put("result", null);

  125. try {

  126. seckillDao.killByProcedure(map);

  127. int result = MapUtils.getInteger(map, "result", -2);

  128. if (result == 1) {

  129. SuccessKilled successKill = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);

  130. return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKill);

  131. } else {

  132. return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));

  133. }

  134. } catch (Exception e) {

  135. logger.error(e.getMessage(), e);

  136. return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);

  137. }

  138. }

  139. }

③ 異常的處理:

a.SeckillCloseException

  1. package com.force4us.exception;

  2. public class SeckillCloseException extends SeckillException{

  3. public SeckillCloseException(String message) {

  4. super(message);

  5. }

  6. public SeckillCloseException(String message, Throwable cause) {

  7. super(message, cause);

  8. }

  9. }

b. SeckillException

  1. package com.force4us.exception;

  2. public class RepeatKillException extends SeckillException{

  3. public RepeatKillException(String message) {

  4. super(message);

  5. }

  6. public RepeatKillException(String message, Throwable cause) {

  7. super(message, cause);

  8. }

  9. }

c. RepeatKillException

  1. package com.force4us.exception;

  2. public class SeckillException extends RuntimeException{

  3. public SeckillException(String message) {

  4. super(message);

  5. }

  6. public SeckillException(String message, Throwable cause) {

  7. super(message, cause);

  8. }

  9. }

④ 列舉SeckillStatEnum

  1. package com.force4us.enums;

  2. public enum SeckillStatEnum {

  3. SUCCESS(1,"秒殺成功"),

  4. END(0,"秒殺結束"),

  5. REPEAT_KILL(-1,"重複秒殺"),

  6. INNER_ERROR(-2,"系統異常"),

  7. DATE_REWRITE(-3,"資料篡改");

  8. private int state;

  9. private String stateInfo;

  10. SeckillStatEnum(int state, String stateInfo){

  11. this.state = state;

  12. this.stateInfo = stateInfo;

  13. }

  14. public int getState() {

  15. return state;

  16. }

  17. public String getStateInfo() {

  18. return stateInfo;

  19. }

  20. public static SeckillStatEnum stateOf(int index){

  21. for(SeckillStatEnum state : values()){

  22. if(state.getState() == index){

  23. return state;

  24. }

  25. }

  26. return null;

  27. }

  28. }

⑤ spring_spring.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 掃描service包下所有使用註解的型別--> <context:component-scan base-package="com.force4us.service"/> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入資料庫連線池 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置基於註解的宣告式事務 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>

4.Web層,JSP頁面和JS

(1) 詳情頁流程邏輯邏輯

(2) 配置web.xml[html] view plain copy

  1. <code class="language-html"><?xml version="1.0" encoding="UTF-8"?>  
  2. <!--  
  3.   Licensed to the Apache Software Foundation (ASF) under one or more  
  4.   contributor license agreements.  See the NOTICE file distributed with  
  5.   this work for additional information regarding copyright ownership.  
  6.   The ASF licenses this file to You under the Apache License, Version 2.0  
  7.   (the "License"); you may not use this file except in compliance with  
  8.   the License.  You may obtain a copy of the License at  
  9.       http://www.apache.org/licenses/LICENSE-2.0  
  10.   Unless required by applicable law or agreed to in writing, software  
  11.   distributed under the License is distributed on an "AS IS" BASIS,  
  12.   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13.   See the License for the specific language governing permissions and  
  14.   limitations under the License.  
  15. -->  
  16. <!--  
  17.   - This is the Cocoon web-app configurations file  
  18.   -  
  19.   - $Id$  
  20.   -->  
  21. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
  22.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  23.          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  
  24.                       http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"  
  25.          version="3.1"  
  26.          metadata-complete="true">  
  27.     <!--用maven建立的web-app需要修改servlet的版本為3.1-->  
  28.     <!--配置DispatcherServlet-->  
  29.     <servlet>  
  30.         <servlet-name>seckill-dispatcher</servlet-name>  
  31.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  32.         <!--  
  33.                     配置SpringMVC 需要配置的檔案  
  34.                     spring-dao.xml,spring-service.xml,spring-web.xml  
  35.                     Mybites -> spring -> springMvc  
  36.                 -->  
  37.         <init-param>  
  38.             <param-name>contextConfigLocation</param-name>  
  39.             <param-value>classpath:spring/spring-*.xml</param-value>  
  40.         </init-param>  
  41.     </servlet>  
  42.     <servlet-mapping>  
  43.         <servlet-name>seckill-dispatcher</servlet-name>  
  44.         <url-pattern>/</url-pattern>  
  45.     </servlet-mapping>  
  46. </web-app></code>  

(3) SeckillResult

  1. package com.force4us.dto;

  2. //將所有的ajax請求返回型別,全部封裝成json資料

  3. public class SeckillResult<T> {

  4. private boolean success;

  5. private T data;

  6. private String error;

  7. public SeckillResult(boolean success, T data) {

  8. this.success = success;

  9. this.data = data;

  10. }

  11. public SeckillResult(boolean success, String error) {

  12. this.success = success;

  13. this.error = error;

  14. }

  15. public boolean isSuccess() {

  16. return success;

  17. }

  18. public void setSuccess(boolean success) {

  19. this.success = success;

  20. }

  21. public T getData() {

  22. return data;

  23. }

  24. public void setData(T data) {

  25. this.data = data;

  26. }

  27. public String getError() {

  28. return error;

  29. }

  30. public void setError(String error) {

  31. this.error = error;

  32. }

  33. }

(4) spring-web.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"

  4. xmlns:context="http://www.springframework.org/schema/context"

  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  6. <!--配置spring mvc-->

  7. <!--1,開啟springmvc註解模式

  8. a.自動註冊DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter

  9. b.預設提供一系列的功能:資料繫結,數字和日期的[email protected],@DateTimeFormat

  10. c:xml,json的預設讀寫支援-->

  11. <mvc:annotation-driven/>

  12. <!--2.靜態資源預設servlet配置-->

  13. <!--

  14. 1).加入對靜態資源處理:js,gif,png

  15. 2).允許使用 "/" 做整體對映

  16. -->

  17. <mvc:default-servlet-handler/>

  18. <!--3:配置JSP 顯示ViewResolver-->

  19. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

  20. <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

  21. <property name="prefix" value="/WEB-INF/jsp/"/>

  22. <property name="suffix" value=".jsp"/>

  23. </bean>

  24. <!--4:掃描web相關的controller-->

  25. <context:component-scan base-package="com.force4us.web"/>

  26. </beans>

(5) SeckillController中:

  1. package com.force4us.web;

  2. import com.force4us.dto.Exposer;

  3. import com.force4us.dto.SeckillExecution;

  4. import com.force4us.dto.SeckillResult;

  5. import com.force4us.entity.Seckill;

  6. import com.force4us.enums.SeckillStatEnum;

  7. import com.force4us.exception.RepeatKillException;

  8. import com.force4us.exception.SeckillCloseException;

  9. import com.force4us.exception.SeckillException;

  10. import com.force4us.service.SeckillService;

  11. import org.springframework.beans.factory.annotation.Autowired;

  12. import org.springframework.stereotype.Controller;

  13. import org.springframework.test.annotation.Repeat;

  14. import org.springframework.ui.Model;

  15. import org.springframework.web.bind.annotation.*;

  16. import java.util.Date;

  17. import java.util.List;

  18. @Controller

  19. @RequestMapping("/seckill")

  20. public class SeckillController {

  21. @Autowired

  22. private SeckillService seckillService;

  23. @RequestMapping(value = "/list",method= RequestMethod.GET)

  24. public String list(Model model) {

  25. List<Seckill> list = seckillService.getSeckillList();

  26. model.addAttribute("list",list);

  27. return "list";

  28. }

  29. @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET)

  30. public String detail(@PathVariable("seckillId") Long seckillId, Model model){

  31. if(seckillId == null){

  32. return "redirect:/seckill/list";

  33. }

  34. Seckill seckill = seckillService.getById(seckillId);

  35. if(seckill == null){

  36. return "forward:/seckill/list";

  37. }

  38. model.addAttribute("seckill", seckill);

  39. return "detail";

  40. }

  41. //ajax ,json暴露秒殺介面的方法

  42. @RequestMapping(value="/{seckillId}/exposer",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})

  43. @ResponseBody

  44. public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId){

  45. SeckillResult<Exposer> result;

  46. try {

  47. Exposer exposer = seckillService.exportSeckillUrl(seckillId);

  48. result = new SeckillResult<Exposer>(true,exposer);

  49. } catch (Exception e) {

  50. e.printStackTrace();

  51. result = new SeckillResult<Exposer>(false,e.getMessage());

  52. }

  53. return result;

  54. }

  55. @RequestMapping(value="/{seckillId}/{md5}/execution", method = RequestMethod.POST,

  56. produces = {"application/json;charset=UTF-8"})

  57. @ResponseBody

  58. public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,

  59. @PathVariable("md5") String md5,

  60. @CookieValue(value="killPhone", required = false) Long phone){

  61. if(phone == null){

  62. return new SeckillResult<SeckillExecution>(false,"未註冊");

  63. }

  64. SeckillResult<SeckillExecution> result;

  65. try {

  66. SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId,phone, md5);

  67. return new SeckillResult<SeckillExecution>(true,execution);

  68. } catch (RepeatKillException e1) {

  69. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);

  70. return new SeckillResult<SeckillExecution>(true,execution);

  71. } catch(SeckillCloseException e2){

  72. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);

  73. return new SeckillResult<SeckillExecution>(true,execution);

  74. }catch(Exception e){

  75. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);

  76. return new SeckillResult<SeckillExecution>(true,execution);

  77. }

  78. }

  79. @RequestMapping(value = "/time/now", method = RequestMethod.GET)

  80. @ResponseBody

  81. public SeckillResult<Long> time(){

  82. Date now = new Date();

  83. return new SeckillResult<Long>(true,now.getTime());

  84. }

  85. @RequestMapping("/test")

  86. public String test(){

  87. return "helloworld";

  88. }

  89. }

(6) list.jsp

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  2. <%@include file="common/tag.jsp"%>

  3. <!DOCTYPE html>

  4. <html lang="zh-CN">

  5. <head>

  6. <meta charset="utf-8">

  7. <meta http-equiv="X-UA-Compatible" content="IE=edge">

  8. <meta name="viewport" content="width=device-width, initial-scale=1">

  9. <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->

  10. <title>秒殺列表頁</title>

  11. <%@include file="/WEB-INF/jsp/common/head.jsp"%>

  12. </head>

  13. <body>

  14. <div class="container">

  15. <div class="panel panel-default">

  16. <div class="panel-heading text-center">

  17. <h2>秒殺列表</h2>

  18. </div>

  19. <div class="panel-body">

  20. <table class="table table-hover">

  21. <thead>

  22. <tr>

  23. <th>名稱</th>

  24. <th>庫存</th>

  25. <th>開始時間</th>

  26. <th>結束時間</th>

  27. <th>建立時間</th>

  28. <th>詳情頁</th>

  29. </tr>

  30. </thead>

  31. <tbody>

  32. <c:forEach items="${list}" var="sk">

  33. <tr>

  34. <td>${sk.name}</td>

  35. <td>${sk.number}</td>

  36. <td>

  37. <fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  38. </td>

  39. <td>

  40. <fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  41. </td>

  42. <td>

  43. <fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  44. </td>

  45. <td><a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">詳情</a></td>

  46. </tr>

  47. </c:forEach>

  48. </tbody>

  49. </table>

  50. </div>

  51. </div>

  52. </div>

  53. </body>

  54. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->

  55. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

  56. <!-- 最新的 Bootstrap 核心 JavaScript 檔案 -->

  57. <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  58. </html>

(7) details.jsp

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  2. <%@include file="common/tag.jsp"%>

  3. <!DOCTYPE html>

  4. <html lang="zh-CN">

  5. <head>

  6. <meta charset="utf-8">

  7. <meta http-equiv="X-UA-Compatible" content="IE=edge">

  8. <meta name="viewport" content="width=device-width, initial-scale=1">

  9. <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->

  10. <title>秒殺詳情頁</title>

  11. <%@include file="common/head.jsp"%>

  12. </head>

  13. <body>

  14. <div class="container">

  15. <div class="panel panel-default text-center">

  16. <div class="pannel-heading">

  17. <h1>${seckill.name}</h1>

  18. </div>

  19. <div class="panel-body">

  20. <h2 class="text-danger">

  21. <%--顯示time圖示--%>

  22. <span class="glyphicon glyphicon-time"></span>

  23. <%--展示倒計時--%>

  24. <span class="glyphicon" id="seckill-box"></span>

  25. </h2>

  26. </div>

  27. </div>

  28. </div>

  29. <%--登入彈出層 輸入電話--%>

  30. <div id="killPhoneModal" class="modal fade">

  31. <div class="modal-dialog">

  32. <div class="modal-content">

  33. <div class="modal-header">

  34. <h3 class="modal-title text-center">

  35. <span class="glyphicon glyphicon-phone"> </span>秒殺電話:

  36. </h3>

  37. </div>

  38. <div class="modal-body">

  39. <div class="row">

  40. <div class="col-xs-8 col-xs-offset-2">

  41. <input type="text" name="killPhone" id="killPhoneKey"

  42. placeholder="填寫手機號^o^" class="form-control">

  43. </div>

  44. </div>

  45. </div>

  46. <div class="modal-footer">

  47. <%--驗證資訊--%>

  48. <span id="killPhoneMessage" class="glyphicon"> </span>

  49. <button type="button" id="killPhoneBtn" class="btn btn-success">

  50. <span class="glyphicon glyphicon-phone"></span>

  51. Submit

  52. </button>

  53. </div>

  54. </div>

  55. </div>

  56. </div>

  57. </body>

  58. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->

  59. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

  60. <!-- 最新的 Bootstrap 核心 JavaScript 檔案 -->

  61. <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjS