├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs └── script │ └── initDB.sql ├── pom.xml ├── seckill-client ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── imooc │ └── client │ └── SeckillService.java ├── seckill-core ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── imooc │ │ ├── aop │ │ └── LogAOP.java │ │ └── web │ │ └── SeckillController.java │ ├── resources │ ├── logback.xml │ └── spring │ │ ├── applicationContext.xml │ │ ├── spring-dubbo-config.xml │ │ ├── spring-dubbo-consumer.xml │ │ └── spring-web.xml │ └── webapp │ ├── WEB-INF │ ├── jsp │ │ ├── common │ │ │ ├── head.jsp │ │ │ └── tag.jsp │ │ ├── detail.jsp │ │ └── list.jsp │ └── web.xml │ └── resources │ └── js │ └── seckill.js ├── seckill-model ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── imooc │ ├── dto │ ├── Exposer.java │ ├── SeckillExecution.java │ └── SeckillResult.java │ ├── entity │ ├── Seckill.java │ └── SuccessKilled.java │ ├── enums │ └── SeckillStateEnum.java │ └── exception │ ├── RepeatKillException.java │ ├── SeckillCloseException.java │ └── SeckillException.java └── seckill-service-provider ├── pom.xml └── src └── main ├── java └── com │ └── imooc │ ├── dao │ ├── SeckillDao.java │ ├── SuccessKilledDao.java │ └── cache │ │ └── RedisDao.java │ └── service │ └── impl │ ├── App.java │ └── SeckillServiceImpl.java ├── resources ├── dubbo.proerpties ├── jdbc.properties ├── logback.xml ├── mapper │ ├── SeckillDao.xml │ └── SuccessKilledDao.xml ├── mybatis-config.xml └── spring │ ├── applicationContext.xml │ ├── spring-dao.xml │ ├── spring-dubbo-config.xml │ ├── spring-dubbo-provider.xml │ ├── spring-service.xml │ └── spring-web.xml └── webapp ├── WEB-INF └── web.xml └── index.jsp /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | branches: [ "master" ] 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '17' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Build with Maven 31 | run: mvn -B package --file pom.xml 32 | 33 | # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive 34 | - name: Update dependency graph 35 | uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | *.idea 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 justinbaby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seckill(Java高并发秒杀API) 2 | 一个spring入门项目,后续将更新spring中级项目 :) 3 | #### 所用技术点 4 | - spring 5 | - springMVC: MVC框架 6 | - Tomcat: web容器 7 | - mybatis: ORM框架 8 | - bootstrap: css/html框架 9 | - JQuery: JS框架 10 | - Redis: NOSQL数据库 11 | - MySQL: 关系型数据库 12 | - Logback: 日志框架 13 | - JUnit: 单元测试 14 | - CDN: 内容分发服务器 15 | - Procedure:数据库存储过程 16 | - Protostuff:Google开发的基于Java语言的序列化库 17 | - ZooKeeper:分布式应用程序协调服务 18 | - DUBBO:分布式应用服务框架 19 | 20 | ## Java高并发秒杀系统API 21 | 22 | 本项目参考慕课网视频(版权方),并在此基础上进行了模块划分,功能添加,欢迎去官网观看!!!! 23 | 24 | ## 安装部署 25 | 26 | #### 软件环境: 27 | 28 | - IDEA 29 | - MySQL 30 | - JDK1.8或以上 31 | - tomcat 8.0 32 | - Redis 33 | - Maven 34 | #### 硬件环境(最小配置): 35 | 36 | - CPU:1核 37 | - 内存:1G 38 | 39 | #### 说明 40 | - seckill-core:秒杀核心模块,部署tomcat启动 41 | - seckill-api:秒杀api模块 42 | - seckill-base:秒杀工具类模块 43 | - seckill-web:秒杀页面模块 44 | 45 | #### 步骤 46 | 1. 创建数据库,导入初始化脚本initDB.sql 47 | 2. 修改系统数据库连接seckill-core/src/main/resources/jdbc.properties 48 | 3. Tomcat运行 49 | 50 | ## FAQ 51 | - Q:为什么我的maven下载依赖jar包这么慢 52 | - A:可以自己下载maven,使用自己的Maven,修改maven安装目录下的/conf/settings.xml,在IDEA或eclipse里配置为默认的设置 53 | ```XML 54 | 55 | nexus-aliyun 56 | central 57 | Nexus aliyun 58 | http://maven.aliyun.com/nexus/content/groups/public 59 | 60 | ``` 61 | 62 | - Q:为什么我的数据库连不上 63 | - A:查看一下jdbc.properties,配置url为你的数据库地址,你的数据库用户名和密码 64 | 65 | - Q:为什控制台报Redis异常 66 | - A:查看一下Redis服务器是否启动,没下载的建议去官网下载window或linux版 67 | 68 | - Q:eclipse怎么导入 69 | - A:去掉.开头的文件就可以导入!!! 70 | -------------------------------------------------------------------------------- /docs/script/initDB.sql: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Source Server : root 4 | Source Server Version : 50538 5 | Source Host : localhost:3306 6 | Source Database : seckill 7 | 8 | Target Server Type : MYSQL 9 | Target Server Version : 50538 10 | File Encoding : 65001 11 | 12 | Date: 2017-02-17 12:05:42 13 | */ 14 | 15 | SET FOREIGN_KEY_CHECKS=0; 16 | 17 | -- ---------------------------- 18 | -- Table structure for seckill 19 | -- ---------------------------- 20 | DROP TABLE IF EXISTS `seckill`; 21 | CREATE TABLE `seckill` ( 22 | `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存ID', 23 | `name` varchar(120) NOT NULL COMMENT '商品名称', 24 | `number` int(11) NOT NULL COMMENT '库存数量', 25 | `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '秒杀开始时间', 26 | `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '秒杀结束时间', 27 | `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间', 28 | PRIMARY KEY (`seckill_id`), 29 | KEY `idx_start_time` (`start_time`), 30 | KEY `idx_end_time` (`end_time`), 31 | KEY `idx_create_time` (`create_time`) 32 | ) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; 33 | 34 | -- ---------------------------- 35 | -- Records of seckill 36 | -- ---------------------------- 37 | INSERT INTO `seckill` VALUES ('1000', '100元秒杀诺基亚', '10000', '2017-02-17 12:02:05', '2019-12-21 00:00:00', '2016-12-21 00:00:00'); 38 | INSERT INTO `seckill` VALUES ('1001', '5000元秒杀iphone7', '1700', '2017-02-17 11:47:36', '2016-04-11 00:00:00', '2016-04-11 00:00:00'); 39 | INSERT INTO `seckill` VALUES ('1002', '1000元秒杀ipad1', '3500', '2017-02-17 11:47:41', '2016-05-12 00:00:00', '2016-05-12 00:00:00'); 40 | INSERT INTO `seckill` VALUES ('1003', '1600元秒杀小米4', '1200', '2017-02-17 11:47:47', '2016-07-23 00:00:00', '2016-07-23 00:00:00'); 41 | INSERT INTO `seckill` VALUES ('1004', '1400元秒杀魅族4', '1099', '2017-02-17 12:02:28', '2018-02-17 11:48:00', '2017-02-17 11:48:00'); 42 | INSERT INTO `seckill` VALUES ('1005', '1400元秒杀小米3', '1100', '2017-02-17 11:48:23', '2016-01-21 00:00:00', '2016-01-21 00:00:00'); 43 | 44 | -- ---------------------------- 45 | -- Table structure for success_killed 46 | -- ---------------------------- 47 | DROP TABLE IF EXISTS `success_killed`; 48 | CREATE TABLE `success_killed` ( 49 | `seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品ID', 50 | `user_phone` bigint(20) NOT NULL COMMENT '用户手机号', 51 | `state` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货', 52 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', 53 | PRIMARY KEY (`seckill_id`,`user_phone`), 54 | KEY `idx_create_time` (`create_time`) 55 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'; 56 | 57 | -- ---------------------------- 58 | -- Records of success_killed 59 | -- ---------------------------- 60 | INSERT INTO `success_killed` VALUES ('1004', '15643645806', '-1', '2017-02-17 12:02:27'); 61 | 62 | -- ---------------------------- 63 | -- View structure for v1 64 | -- ---------------------------- 65 | DROP VIEW IF EXISTS `v1`; 66 | CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` VIEW `v1` AS SELECT *FROM seckill ; 67 | 68 | -- ---------------------------- 69 | -- Procedure structure for execute_seckill 70 | -- ---------------------------- 71 | DROP PROCEDURE IF EXISTS `execute_seckill`; 72 | DELIMITER ;; 73 | CREATE DEFINER=`root`@`localhost` PROCEDURE `execute_seckill`(IN v_seckill_id bigint, IN v_phone BIGINT, 74 | IN v_kill_time TIMESTAMP, OUT r_result INT) 75 | BEGIN 76 | DECLARE insert_count INT DEFAULT 0; 77 | START TRANSACTION; 78 | INSERT ignore INTO success_killed (seckill_id, user_phone, create_time) 79 | VALUES(v_seckill_id, v_phone, v_kill_time); 80 | SELECT ROW_COUNT() INTO insert_count; 81 | IF (insert_count = 0) THEN 82 | ROLLBACK; 83 | SET r_result = -1; 84 | ELSEIF (insert_count < 0) THEN 85 | ROLLBACK ; 86 | SET r_result = -2; 87 | ELSE 88 | UPDATE seckill SET number = number - 1 89 | WHERE seckill_id = v_seckill_id AND end_time > v_kill_time 90 | AND start_time < v_kill_time AND number > 0; 91 | SELECT ROW_COUNT() INTO insert_count; 92 | IF (insert_count = 0) THEN 93 | ROLLBACK; 94 | SET r_result = 0; 95 | ELSEIF (insert_count < 0) THEN 96 | ROLLBACK; 97 | SET r_result = -2; 98 | ELSE 99 | COMMIT; 100 | SET r_result = 1; 101 | END IF; 102 | END IF; 103 | END 104 | ;; 105 | DELIMITER ; 106 | SET FOREIGN_KEY_CHECKS=1; 107 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.imooc 5 | seckill 6 | pom 7 | 0.0.1-SNAPSHOT 8 | 9 | seckill-core 10 | seckill-model 11 | seckill-client 12 | 13 | seckill Maven Webapp 14 | http://maven.apache.org 15 | 16 | 17 | 18 | junit 19 | junit 20 | 4.13.1 21 | 22 | 23 | 24 | 25 | 26 | ch.qos.logback 27 | logback-classic 28 | 1.2.0 29 | 30 | 31 | 32 | 33 | mysql 34 | mysql-connector-java 35 | 8.0.16 36 | runtime 37 | 38 | 39 | c3p0 40 | c3p0 41 | 0.9.1.2 42 | 43 | 44 | com.alibaba 45 | druid 46 | 1.0.13 47 | 48 | 49 | 50 | 51 | org.mybatis 52 | mybatis 53 | 3.5.6 54 | 55 | 56 | org.mybatis 57 | mybatis-spring 58 | 1.3.2 59 | 60 | 61 | 62 | 63 | taglibs 64 | standard 65 | 1.1.2 66 | 67 | 68 | jstl 69 | jstl 70 | 1.2 71 | 72 | 73 | com.fasterxml.jackson.core 74 | jackson-databind 75 | 2.9.10.7 76 | 77 | 78 | javax.servlet 79 | javax.servlet-api 80 | 3.1.0 81 | 82 | 83 | 84 | 85 | 86 | org.springframework 87 | spring-core 88 | 4.3.21.RELEASE 89 | 90 | 91 | org.springframework 92 | spring-beans 93 | 4.1.7.RELEASE 94 | 95 | 96 | org.springframework 97 | spring-context 98 | 4.1.7.RELEASE 99 | 100 | 101 | 102 | org.springframework 103 | spring-jdbc 104 | 4.1.7.RELEASE 105 | 106 | 107 | org.springframework 108 | spring-tx 109 | 4.1.7.RELEASE 110 | 111 | 112 | 113 | org.springframework 114 | spring-web 115 | 4.1.7.RELEASE 116 | 117 | 118 | org.springframework 119 | spring-webmvc 120 | 4.1.7.RELEASE 121 | 122 | 123 | 124 | org.springframework 125 | spring-test 126 | 4.1.7.RELEASE 127 | 128 | 129 | org.springframework 130 | spring-aop 131 | 4.1.7.RELEASE 132 | 133 | 134 | org.springframework 135 | spring-aspects 136 | 4.2.0.RELEASE 137 | 138 | 139 | 140 | 141 | redis.clients 142 | jedis 143 | 2.7.3 144 | 145 | 146 | com.dyuproject.protostuff 147 | protostuff-core 148 | 1.0.8 149 | 150 | 151 | javax 152 | javaee-api 153 | 7.0 154 | 155 | 156 | com.dyuproject.protostuff 157 | protostuff-runtime 158 | 1.0.8 159 | 160 | 161 | 162 | commons-collections 163 | commons-collections 164 | 3.2.2 165 | 166 | 167 | 168 | seckill 169 | 170 | 171 | -------------------------------------------------------------------------------- /seckill-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | seckill 7 | com.imooc 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | seckill-client 13 | 14 | 15 | com.imooc 16 | seckill-model 17 | 0.0.1-SNAPSHOT 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /seckill-client/src/main/java/com/imooc/client/SeckillService.java: -------------------------------------------------------------------------------- 1 | package com.imooc.client; 2 | 3 | import com.imooc.dto.Exposer; 4 | import com.imooc.dto.SeckillExecution; 5 | import com.imooc.entity.Seckill; 6 | import com.imooc.exception.RepeatKillException; 7 | import com.imooc.exception.SeckillCloseException; 8 | import com.imooc.exception.SeckillException; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常) 14 | * 15 | * @author yan 16 | */ 17 | public interface SeckillService { 18 | 19 | /** 20 | * 查询所有秒杀记录 21 | * 22 | * @return 23 | */ 24 | List getSeckillList(); 25 | 26 | /** 27 | * 查询单个秒杀记录 28 | * 29 | * @param seckillId 30 | * @return 31 | */ 32 | Seckill getById(long seckillId); 33 | 34 | /** 35 | * 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间 36 | * 37 | * @param seckillId 38 | * @return 39 | */ 40 | Exposer exportSeckillUrl(long seckillId); 41 | 42 | /** 43 | * 执行秒杀操作 44 | * 45 | * @param seckillId 46 | * @param userPhone 47 | * @param md5 48 | * @return 49 | * @throws SeckillException 50 | * @throws RepeatKillException 51 | * @throws SeckillCloseException 52 | */ 53 | SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) 54 | throws SeckillException, RepeatKillException, SeckillCloseException; 55 | 56 | /** 57 | * 执行秒杀操作by存储过程 58 | * 59 | * @param seckillId 60 | * @param userPhone 61 | * @param md5 62 | * @return 63 | * @throws SeckillException 64 | * @throws RepeatKillException 65 | * @throws SeckillCloseException 66 | */ 67 | SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) 68 | throws SeckillException, RepeatKillException, SeckillCloseException; 69 | 70 | } -------------------------------------------------------------------------------- /seckill-core/.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /seckill-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | seckill 7 | com.imooc 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | seckill-core 13 | war 14 | seckill-dubbo-consumer Maven Webapp 15 | http://maven.apache.org 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 22 | 1.8 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | 30 | com.imooc 31 | seckill-model 32 | 0.0.1-SNAPSHOT 33 | 34 | 35 | com.imooc 36 | seckill-client 37 | 0.0.1-SNAPSHOT 38 | 39 | 40 | 41 | com.alibaba 42 | dubbo 43 | 2.5.7 44 | 45 | 46 | spring 47 | org.springframework 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.zookeeper 55 | zookeeper 56 | 3.5.3-beta 57 | 58 | 59 | log4j 60 | log4j 61 | 62 | 63 | 64 | 65 | com.101tec 66 | zkclient 67 | 0.8 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /seckill-core/src/main/java/com/imooc/aop/LogAOP.java: -------------------------------------------------------------------------------- 1 | package com.imooc.aop; 2 | 3 | 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Pointcut; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.validation.BindingResult; 12 | 13 | /** 14 | * @author yan 15 | *

16 | * 采用AOP的方式处理参数问题。 17 | */ 18 | @Component 19 | @Aspect 20 | public class LogAOP { 21 | 22 | private final Logger LOG = LoggerFactory.getLogger(this.getClass()); 23 | 24 | @Pointcut("execution(* com.imooc.web.*.*(..))") 25 | public void aopMethod() { 26 | } 27 | 28 | @Around("aopMethod()") 29 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 30 | String classType = joinPoint.getTarget().getClass().getName(); 31 | //运用反射的原理创建对象 32 | Class clazz = Class.forName(classType); 33 | String clazzName = clazz.getName(); 34 | String clazzSimpleName = clazz.getSimpleName(); 35 | String methodName = joinPoint.getSignature().getName(); 36 | Logger logger = LoggerFactory.getLogger(clazzName); 37 | logger.info("clazzName: " + clazzName + ", methodName:" + methodName); 38 | long start = System.currentTimeMillis(); 39 | LOG.info("before method invoking!"); 40 | BindingResult bindingResult = null; 41 | System.out.println("---------------"); 42 | System.out.println("调用类:" + clazzSimpleName); 43 | System.out.println("调用方法:" + methodName); 44 | return joinPoint.proceed(); 45 | } 46 | } -------------------------------------------------------------------------------- /seckill-core/src/main/java/com/imooc/web/SeckillController.java: -------------------------------------------------------------------------------- 1 | package com.imooc.web; 2 | 3 | import com.imooc.dto.SeckillExecution; 4 | import com.imooc.exception.SeckillCloseException; 5 | import com.imooc.dto.Exposer; 6 | import com.imooc.dto.SeckillResult; 7 | import com.imooc.entity.Seckill; 8 | import com.imooc.enums.SeckillStateEnum; 9 | import com.imooc.exception.RepeatKillException; 10 | import com.imooc.client.SeckillService; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.ui.Model; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | @Controller // @Service @Componet 22 | @RequestMapping("/seckill") // url:/模块/资源/{id}/细分 /seckill/list 23 | public class SeckillController { 24 | 25 | //sb 26 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 27 | 28 | @Autowired 29 | private SeckillService seckillService; 30 | //http://localhost:8080/seckill/seckill/list 31 | @RequestMapping(value = "/list", method = RequestMethod.GET) 32 | public String list(Model model) { 33 | // 获取列表页 34 | List list = seckillService.getSeckillList(); 35 | model.addAttribute("list", list); 36 | // list.jsp + model = ModelAndView 37 | return "list";// WEB-INF/jsp/"list".jsp 38 | } 39 | 40 | @RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET) 41 | public String detail(@PathVariable("seckillId") Long seckillId, Model model) { 42 | if (seckillId == null) { 43 | return "redirect:/seckill/list"; 44 | } 45 | Seckill seckill = seckillService.getById(seckillId); 46 | if (seckill == null) { 47 | return "forward:/seckill/list"; 48 | } 49 | model.addAttribute("seckill", seckill); 50 | return "detail"; 51 | } 52 | 53 | // ajax json 54 | @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = { 55 | "application/json; charset=utf-8" }) 56 | @ResponseBody 57 | public SeckillResult exposer(@PathVariable("seckillId") Long seckillId) { 58 | SeckillResult result; 59 | try { 60 | Exposer exposer = seckillService.exportSeckillUrl(seckillId); 61 | result = new SeckillResult(true, exposer); 62 | } catch (Exception e) { 63 | logger.error(e.getMessage(), e); 64 | result = new SeckillResult(false, e.getMessage()); 65 | } 66 | return result; 67 | } 68 | 69 | @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = { 70 | "application/json; charset=utf-8" }) 71 | @ResponseBody 72 | public SeckillResult execute(@PathVariable("seckillId") Long seckillId, 73 | @PathVariable("md5") String md5, @CookieValue(value = "killPhone", required = false) Long phone) { 74 | // springmvc valid 75 | if (phone == null) { 76 | return new SeckillResult(false, "未注册"); 77 | } 78 | try { 79 | // 存储过程调用 80 | SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5); 81 | return new SeckillResult(true, execution); 82 | } catch (RepeatKillException e) { 83 | SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL); 84 | return new SeckillResult(true, execution); 85 | } catch (SeckillCloseException e) { 86 | SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END); 87 | return new SeckillResult(true, execution); 88 | } catch (Exception e) { 89 | logger.error(e.getMessage(), e); 90 | SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR); 91 | return new SeckillResult(true, execution); 92 | } 93 | } 94 | 95 | @RequestMapping(value = "/time/now", method = RequestMethod.GET) 96 | @ResponseBody 97 | public SeckillResult time() { 98 | Date now = new Date(); 99 | return new SeckillResult(true, now.getTime()); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /seckill-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /seckill-core/src/main/resources/spring/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /seckill-core/src/main/resources/spring/spring-dubbo-config.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /seckill-core/src/main/resources/spring/spring-dubbo-consumer.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /seckill-core/src/main/resources/spring/spring-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/WEB-INF/jsp/common/head.jsp: -------------------------------------------------------------------------------- 1 | <% 2 | String path = request.getContextPath(); 3 | String basePath = request.getScheme() + "://" 4 | + request.getServerName() + ":" + request.getServerPort() 5 | + path + "/"; 6 | pageContext.setAttribute("basePath",basePath); 7 | %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 24 | -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/WEB-INF/jsp/common/tag.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/WEB-INF/jsp/detail.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | 4 | 5 | 6 | <%@include file="common/head.jsp"%> 7 | 秒杀详情页 8 | 9 | 10 | 11 |

12 |
13 |
14 |

${seckill.name}

15 |
16 |
17 |

18 | 19 | 20 | 21 | 22 |

23 |
24 |
25 |
26 | 27 | 28 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 75 | -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/WEB-INF/jsp/list.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | 4 | <%@include file="common/tag.jsp" %> 5 | 6 | 7 | 8 | <%@include file="common/head.jsp"%> 9 | 秒杀列表页 10 | 11 | 12 | 13 |
14 |
15 |
16 |

秒杀列表

17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 44 | 47 | 48 | 49 | 50 |
名称库存开始时间结束时间创建时间详情页
${sk.name}${sk.number} 36 | 37 | 39 | 40 | 42 | 43 | 45 | link 46 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | seckill-dispatcher 9 | org.springframework.web.servlet.DispatcherServlet 10 | 14 | 15 | contextConfigLocation 16 | classpath:spring/applicationContext.xml 17 | 18 | 19 | 20 | seckill-dispatcher 21 | 22 | / 23 | 24 | 25 | contextConfigLocation 26 | classpath:spring/applicationContext.xml 27 | 28 | 29 | org.springframework.web.context.ContextLoaderListener 30 | 31 | 32 | -------------------------------------------------------------------------------- /seckill-core/src/main/webapp/resources/js/seckill.js: -------------------------------------------------------------------------------- 1 | // 存放主要交换逻辑js代码 2 | // javascript 模块化 3 | var seckill = { 4 | // 封装秒杀相关ajax的url 5 | URL : { 6 | basePath : function() { 7 | return $('#basePath').val(); 8 | }, 9 | now : function() { 10 | return seckill.URL.basePath() + 'seckill/time/now'; 11 | }, 12 | exposer : function(seckillId) { 13 | return seckill.URL.basePath() + 'seckill/' + seckillId + '/exposer'; 14 | }, 15 | execution : function(seckillId, md5) { 16 | return seckill.URL.basePath() + 'seckill/' + seckillId + '/' + md5 + '/execution'; 17 | } 18 | }, 19 | // 处理秒杀逻辑 20 | handleSeckill : function(seckillId, node) { 21 | // 获取秒杀地址,控制显示逻辑,执行秒杀 22 | node.hide().html(''); 23 | console.log('exposerUrl=' + seckill.URL.exposer(seckillId));//TODO 24 | $.post(seckill.URL.exposer(seckillId), {}, function(result) { 25 | // 在回调函数中,执行交互流程 26 | if (result && result['success']) { 27 | var exposer = result['data']; 28 | if (exposer['exposed']) { 29 | // 开启秒杀 30 | var md5 = exposer['md5']; 31 | var killUrl = seckill.URL.execution(seckillId, md5); 32 | console.log('killUrl=' + killUrl);//TODO 33 | $('#killBtn').one('click', function() { 34 | // 执行秒杀请求 35 | // 1.先禁用按钮 36 | $(this).addClass('disabled'); 37 | // 2.发送秒杀请求 38 | $.post(killUrl, {}, function(result) { 39 | if (result && result['success']) { 40 | var killResult = result['data']; 41 | var state = killResult['state']; 42 | var stateInfo = killResult['stateInfo']; 43 | // 3.显示秒杀结果 44 | node.html('' + stateInfo + ''); 45 | } 46 | }); 47 | }); 48 | node.show(); 49 | } else { 50 | // 未开启秒杀 51 | var now = exposer['now']; 52 | var start = exposer['start']; 53 | var end = exposer['end']; 54 | // 重新计算计时逻辑 55 | seckill.countdown(seckillId, now, start, end); 56 | } 57 | } else { 58 | console.log('result=' + result); 59 | } 60 | }); 61 | }, 62 | // 验证手机号 63 | validatePhone : function(phone) { 64 | if (phone && phone.length == 11 && !isNaN(phone)) { 65 | return true; 66 | } else { 67 | return false; 68 | } 69 | }, 70 | // 倒计时 71 | countdown : function(seckillId, nowTime, startTime, endTime) { 72 | // 时间判断 73 | var seckillBox = $('#seckillBox'); 74 | if (nowTime > endTime) { 75 | // 秒杀结束 76 | seckillBox.html('秒杀结束!'); 77 | } else if (nowTime < startTime) { 78 | // 秒杀未开始,计时事件绑定 79 | var killTime = new Date(startTime + 1000); 80 | seckillBox.countdown(killTime, function(event) { 81 | // 时间格式 82 | var format = event.strftime('秒杀倒计时:%D天 %H时 %M分 %S秒'); 83 | seckillBox.html(format); 84 | // 时间完成后回调事件 85 | }).on('finish.countdown', function() { 86 | // 获取秒杀地址,控制显示逻辑,执行秒杀 87 | seckill.handleSeckill(seckillId, seckillBox); 88 | }); 89 | } else { 90 | // 秒杀开始 91 | seckill.handleSeckill(seckillId ,seckillBox); 92 | } 93 | }, 94 | // 详情页秒杀逻辑 95 | detail : { 96 | // 详情页初始化 97 | init : function(params) { 98 | // 用户手机验证和登录,计时交互 99 | // 规划我们的交互流程 100 | // 在cookie中查找手机号 101 | var killPhone = $.cookie('killPhone'); 102 | var startTime = params['startTime']; 103 | var endTime = params['endTime']; 104 | var seckillId = params['seckillId']; 105 | // 验证手机号 106 | if (!seckill.validatePhone(killPhone)) { 107 | // 绑定phone 108 | // 控制输出 109 | var killPhoneModal = $('#killPhoneModal'); 110 | killPhoneModal.modal({ 111 | show : true,// 显示弹出层 112 | backdrop : 'static',// 禁止位置关闭 113 | keyboard : false 114 | // 关闭键盘事件 115 | }) 116 | $('#killPhoneBtn').click(function() { 117 | var inputPhone = $('#killphoneKey').val(); 118 | console.log('inputPhone='+inputPhone);//TODO 119 | if (seckill.validatePhone(inputPhone)) { 120 | // 电话写入cookie 121 | $.cookie('killPhone', inputPhone, { 122 | expires : 7, 123 | path : '/seckill' 124 | }); 125 | // 刷新页面 126 | window.location.reload(); 127 | } else { 128 | $('#killphoneMessage').hide().html('').show(300); 129 | } 130 | }); 131 | } 132 | // 已经登录 133 | // 计时交互 134 | var startTime = params['startTime']; 135 | var endTime = params['endTime']; 136 | var seckillId = params['seckillId']; 137 | $.get(seckill.URL.now(), {}, function(result) { 138 | if (result && result['success']) { 139 | var nowTime = result['data']; 140 | // 时间判断,计时交互 141 | seckill.countdown(seckillId, nowTime, startTime, endTime); 142 | } else { 143 | console.log(result['reult:'] + result); 144 | } 145 | }); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /seckill-model/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | seckill 7 | com.imooc 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | seckill-model 13 | 14 | 15 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/dto/Exposer.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 暴露秒杀接口DTO 7 | * 8 | * @author yan 9 | */ 10 | public class Exposer implements Serializable { 11 | 12 | // 是否开启秒杀 13 | private boolean exposed; 14 | 15 | // 一种加密措施 16 | private String md5; 17 | 18 | // id 19 | private long seckillId; 20 | 21 | // 系统当前时间(毫秒) 22 | private long now; 23 | 24 | // 开启时间 25 | private long start; 26 | 27 | // 结束时间 28 | private long end; 29 | 30 | public Exposer(boolean exposed, String md5, long seckillId) { 31 | this.exposed = exposed; 32 | this.md5 = md5; 33 | this.seckillId = seckillId; 34 | } 35 | 36 | public Exposer(boolean exposed, long seckillId, long now, long start, long end) { 37 | this.exposed = exposed; 38 | this.seckillId = seckillId; 39 | this.now = now; 40 | this.start = start; 41 | this.end = end; 42 | } 43 | 44 | public Exposer(boolean exposed, long seckillId) { 45 | this.exposed = exposed; 46 | this.seckillId = seckillId; 47 | } 48 | 49 | public boolean isExposed() { 50 | return exposed; 51 | } 52 | 53 | public void setExposed(boolean exposed) { 54 | this.exposed = exposed; 55 | } 56 | 57 | public String getMd5() { 58 | return md5; 59 | } 60 | 61 | public void setMd5(String md5) { 62 | this.md5 = md5; 63 | } 64 | 65 | public long getSeckillId() { 66 | return seckillId; 67 | } 68 | 69 | public void setSeckillId(long seckillId) { 70 | this.seckillId = seckillId; 71 | } 72 | 73 | public long getNow() { 74 | return now; 75 | } 76 | 77 | public void setNow(long now) { 78 | this.now = now; 79 | } 80 | 81 | public long getStart() { 82 | return start; 83 | } 84 | 85 | public void setStart(long start) { 86 | this.start = start; 87 | } 88 | 89 | public long getEnd() { 90 | return end; 91 | } 92 | 93 | public void setEnd(long end) { 94 | this.end = end; 95 | } 96 | 97 | //重写toString方法 98 | @Override 99 | public String toString() { 100 | return "Exposer [exposed=" + exposed + ", md5=" + md5 + ", seckillId=" + seckillId + ", now=" + now + ", start=" 101 | + start + ", end=" + end + "]"; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/dto/SeckillExecution.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dto; 2 | 3 | import com.imooc.entity.SuccessKilled; 4 | import com.imooc.enums.SeckillStateEnum; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 封装秒杀执行后结果 10 | * 11 | * @author yan 12 | */ 13 | public class SeckillExecution implements Serializable { 14 | 15 | private Long seckillId; 16 | 17 | // 秒杀执行结果状态 18 | private Integer state; 19 | 20 | // 状态标识 21 | private String stateInfo; 22 | 23 | // 秒杀成功对象 24 | private SuccessKilled successKilled; 25 | 26 | public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum, SuccessKilled successKilled) { 27 | this.seckillId = seckillId; 28 | this.state = stateEnum.getState(); 29 | this.stateInfo = stateEnum.getStateInfo(); 30 | this.successKilled = successKilled; 31 | } 32 | 33 | public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum) { 34 | this.seckillId = seckillId; 35 | if(stateEnum!=null){ 36 | this.state = stateEnum.getState(); 37 | this.stateInfo = stateEnum.getStateInfo(); 38 | } 39 | } 40 | 41 | public Long getSeckillId() { 42 | return seckillId; 43 | } 44 | 45 | public void setSeckillId(Long seckillId) { 46 | this.seckillId = seckillId; 47 | } 48 | 49 | public Integer getState() { 50 | return state; 51 | } 52 | 53 | public void setState(Integer state) { 54 | this.state = state; 55 | } 56 | 57 | public String getStateInfo() { 58 | return stateInfo; 59 | } 60 | 61 | public void setStateInfo(String stateInfo) { 62 | this.stateInfo = stateInfo; 63 | } 64 | 65 | public SuccessKilled getSuccessKilled() { 66 | return successKilled; 67 | } 68 | 69 | public void setSuccessKilled(SuccessKilled successKilled) { 70 | this.successKilled = successKilled; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "SeckillExecution [seckillId=" + seckillId + ", state=" + state + ", stateInfo=" + stateInfo 76 | + ", successKilled=" + successKilled + "]"; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/dto/SeckillResult.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dto; 2 | 3 | //封装json结果 4 | public class SeckillResult { 5 | 6 | private boolean success; 7 | 8 | private T data; 9 | 10 | private String error; 11 | 12 | public SeckillResult(boolean success, String error) { 13 | this.success = success; 14 | this.error = error; 15 | } 16 | 17 | public SeckillResult(boolean success, T data) { 18 | this.success = success; 19 | this.data = data; 20 | } 21 | 22 | public boolean isSuccess() { 23 | return success; 24 | } 25 | 26 | public void setSuccess(boolean success) { 27 | this.success = success; 28 | } 29 | 30 | public T getData() { 31 | return data; 32 | } 33 | 34 | public void setData(T data) { 35 | this.data = data; 36 | } 37 | 38 | public String getError() { 39 | return error; 40 | } 41 | 42 | public void setError(String error) { 43 | this.error = error; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "SeckillResult [success=" + success + ", data=" + data + ", error=" + error + "]"; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/entity/Seckill.java: -------------------------------------------------------------------------------- 1 | package com.imooc.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 秒杀库存实体 8 | * 9 | * @author yan 10 | */ 11 | public class Seckill implements Serializable { 12 | private long seckillId; 13 | 14 | private String name; 15 | 16 | private int number; 17 | 18 | private Date startTime; 19 | 20 | private Date endTime; 21 | 22 | private Date createTime; 23 | 24 | public long getSeckillId() { 25 | return seckillId; 26 | } 27 | 28 | public void setSeckillId(long seckillId) { 29 | this.seckillId = seckillId; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public int getNumber() { 41 | return number; 42 | } 43 | 44 | public void setNumber(int number) { 45 | this.number = number; 46 | } 47 | 48 | public Date getStartTime() { 49 | return startTime; 50 | } 51 | 52 | public void setStartTime(Date startTime) { 53 | this.startTime = startTime; 54 | } 55 | 56 | public Date getEndTime() { 57 | return endTime; 58 | } 59 | 60 | public void setEndTime(Date endTime) { 61 | this.endTime = endTime; 62 | } 63 | 64 | public Date getCreateTime() { 65 | return createTime; 66 | } 67 | 68 | public void setCreateTime(Date createTime) { 69 | this.createTime = createTime; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime 75 | + ", endTime=" + endTime + ", createTime=" + createTime + "]"; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/entity/SuccessKilled.java: -------------------------------------------------------------------------------- 1 | package com.imooc.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 成功秒杀实体 8 | * 9 | * @author yan 10 | */ 11 | public class SuccessKilled implements Serializable { 12 | 13 | private long seckillId; 14 | 15 | private long userPhone; 16 | 17 | private short state; 18 | 19 | private Date creteTime; 20 | 21 | // 多对一的复合属性 22 | private Seckill seckill; 23 | 24 | public long getSeckillId() { 25 | return seckillId; 26 | } 27 | 28 | public void setSeckillId(long seckillId) { 29 | this.seckillId = seckillId; 30 | } 31 | 32 | public long getUserPhone() { 33 | return userPhone; 34 | } 35 | 36 | public void setUserPhone(long userPhone) { 37 | this.userPhone = userPhone; 38 | } 39 | 40 | public short getState() { 41 | return state; 42 | } 43 | 44 | public void setState(short state) { 45 | this.state = state; 46 | } 47 | 48 | public Date getCreteTime() { 49 | return creteTime; 50 | } 51 | 52 | public void setCreteTime(Date creteTime) { 53 | this.creteTime = creteTime; 54 | } 55 | 56 | public Seckill getSeckill() { 57 | return seckill; 58 | } 59 | 60 | public void setSeckill(Seckill seckill) { 61 | this.seckill = seckill; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state 67 | + ", creteTime=" + creteTime + "]"; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/enums/SeckillStateEnum.java: -------------------------------------------------------------------------------- 1 | package com.imooc.enums; 2 | 3 | /** 4 | * 使用枚举表述常量数据字典 5 | * 6 | * @author yan 7 | */ 8 | public enum SeckillStateEnum { 9 | 10 | SUCCESS(1, "秒杀成功"), END(0, "秒杀结束"), 11 | 12 | REPEAT_KILL(-1, "重复秒杀"), 13 | 14 | INNER_ERROR(-2, "系统异常"), 15 | 16 | DATA_REWRITE(-3, "数据篡改"); 17 | 18 | private Integer state; 19 | 20 | private String stateInfo; 21 | 22 | private SeckillStateEnum(int state, String stateInfo) { 23 | this.state = state; 24 | this.stateInfo = stateInfo; 25 | } 26 | 27 | public Integer getState() { 28 | return state; 29 | } 30 | 31 | public String getStateInfo() { 32 | return stateInfo; 33 | } 34 | 35 | public static SeckillStateEnum stateOf(Integer index) { 36 | for (SeckillStateEnum state : values()) { 37 | if (state.getState().equals(index)) { 38 | return state; 39 | } 40 | } 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/exception/RepeatKillException.java: -------------------------------------------------------------------------------- 1 | package com.imooc.exception; 2 | 3 | /** 4 | * 重复秒杀异常(运行期异常) 5 | * 6 | * @author yan 7 | */ 8 | public class RepeatKillException extends SeckillException { 9 | public RepeatKillException(String message) { 10 | super(message); 11 | } 12 | 13 | public RepeatKillException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/exception/SeckillCloseException.java: -------------------------------------------------------------------------------- 1 | package com.imooc.exception; 2 | 3 | /** 4 | * 秒杀关闭异常 5 | * 6 | * @author yan 7 | */ 8 | public class SeckillCloseException extends SeckillException { 9 | 10 | public SeckillCloseException(String message) { 11 | super(message); 12 | } 13 | 14 | public SeckillCloseException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /seckill-model/src/main/java/com/imooc/exception/SeckillException.java: -------------------------------------------------------------------------------- 1 | package com.imooc.exception; 2 | 3 | /** 4 | * 秒杀相关业务异常 5 | * 6 | * @author yan 7 | */ 8 | public class SeckillException extends RuntimeException { 9 | 10 | public SeckillException(String message) { 11 | super(message); 12 | } 13 | 14 | public SeckillException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /seckill-service-provider/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | seckill 7 | com.imooc 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | seckill-service 13 | war 14 | seckill-dubbo-consumer Maven Webapp 15 | http://maven.apache.org 16 | 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-compiler-plugin 22 | 23 | 1.6 24 | 1.6 25 | 26 | 27 | 28 | 29 | 30 | 31 | com.imooc 32 | seckill-model 33 | 0.0.1-SNAPSHOT 34 | 35 | 36 | com.imooc 37 | seckill-client 38 | 0.0.1-SNAPSHOT 39 | 40 | 41 | 42 | com.alibaba 43 | dubbo 44 | 2.5.7 45 | 46 | 47 | spring 48 | org.springframework 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.zookeeper 56 | zookeeper 57 | 3.5.3-beta 58 | 59 | 60 | log4j 61 | log4j 62 | 63 | 64 | 65 | 66 | com.101tec 67 | zkclient 68 | 0.8 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/java/com/imooc/dao/SeckillDao.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dao; 2 | 3 | import com.imooc.entity.Seckill; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * 秒杀库存DAO接口 12 | * 13 | * @author yan 14 | */ 15 | public interface SeckillDao { 16 | 17 | /** 18 | * 减库存 19 | * 20 | * @param seckillId 21 | * @param killTime 22 | * @return 如果影响行数等于>1,表示更新的记录行数 23 | */ 24 | int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); 25 | 26 | /** 27 | * 根据id查询秒杀对象 28 | * 29 | * @param seckillId 30 | * @return 31 | */ 32 | Seckill queryById(long seckillId); 33 | 34 | /** 35 | * 根据偏移量查询秒杀商品列表 36 | * 37 | * @param offset 38 | * @param limit 39 | * @return 40 | */ 41 | List queryAll(@Param("offset") int offset, @Param("limit") int limit); 42 | 43 | /** 44 | * 使用存储过程执行秒杀 45 | * 46 | * @param paramMap 47 | */ 48 | void killByProcedure(Map paramMap); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/java/com/imooc/dao/SuccessKilledDao.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dao; 2 | 3 | import com.imooc.entity.SuccessKilled; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | public interface SuccessKilledDao { 7 | 8 | /** 9 | * 插入购买明细,可过滤重复 10 | * 11 | * @param seckillId 12 | * @param userPhone 13 | * @return 插入的行数 14 | */ 15 | int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); 16 | 17 | /** 18 | * 根据id查询SuccessKilled并携带秒杀产品对象实体 19 | * 20 | * @param seckillId 21 | * @param userPhone 22 | * @return 23 | */ 24 | SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/java/com/imooc/dao/cache/RedisDao.java: -------------------------------------------------------------------------------- 1 | package com.imooc.dao.cache; 2 | 3 | import com.dyuproject.protostuff.LinkedBuffer; 4 | import com.dyuproject.protostuff.ProtostuffIOUtil; 5 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 6 | import com.imooc.entity.Seckill; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisPool; 11 | 12 | public class RedisDao { 13 | 14 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 15 | 16 | private final JedisPool jedisPool; 17 | 18 | private RuntimeSchema schema = RuntimeSchema.createFrom(Seckill.class); 19 | 20 | public RedisDao(String ip, int port) { 21 | jedisPool = new JedisPool(ip, port); 22 | } 23 | 24 | public Seckill getSeckill(long seckillId) { 25 | // redis操作逻辑 26 | try { 27 | Jedis jedis = jedisPool.getResource(); 28 | try { 29 | String key = "seckill:" + seckillId; 30 | // 并没有实现内部序列化操作 31 | // get -> byte[] -> 反序列化 -> object[Seckill] 32 | // 采用自定义序列化 33 | // protostuff : pojo. 34 | byte[] bytes = jedis.get(key.getBytes()); 35 | if (bytes != null) { 36 | Seckill seckill = schema.newMessage(); 37 | ProtostuffIOUtil.mergeFrom(bytes, seckill, schema); 38 | // seckill被反序列化 39 | return seckill; 40 | } 41 | } finally { 42 | jedis.close(); 43 | } 44 | } catch (Exception e) { 45 | logger.error(e.getMessage(), e); 46 | } 47 | return null; 48 | } 49 | 50 | public String putSeckill(Seckill seckill) { 51 | // set Object(Seckill) -> 序列号 -> byte[] 52 | try { 53 | Jedis jedis = jedisPool.getResource(); 54 | try { 55 | String key = "seckill:" + seckill.getSeckillId(); 56 | byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema, 57 | LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); 58 | // 超时缓存 59 | int timeout = 60 * 60; 60 | String result = jedis.setex(key.getBytes(), timeout, bytes); 61 | return result; 62 | } finally { 63 | jedis.close(); 64 | } 65 | } catch (Exception e) { 66 | logger.error(e.getMessage(), e); 67 | } 68 | return null; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/java/com/imooc/service/impl/App.java: -------------------------------------------------------------------------------- 1 | package com.imooc.service.impl; 2 | 3 | import com.imooc.client.SeckillService; 4 | import com.imooc.entity.Seckill; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | public class App { 11 | public static void main(String[] args) throws IOException { 12 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/applicationContext.xml"}); 13 | context.start(); 14 | SeckillService seckillService = context.getBean("seckillService",SeckillService.class); 15 | List getSeckillList = seckillService.getSeckillList(); 16 | System.out.println(getSeckillList); 17 | System.out.println("press any key to exit."); 18 | System.in.read(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/java/com/imooc/service/impl/SeckillServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.imooc.service.impl; 2 | 3 | import com.imooc.dao.SeckillDao; 4 | import com.imooc.dao.SuccessKilledDao; 5 | import com.imooc.dao.cache.RedisDao; 6 | import com.imooc.dto.Exposer; 7 | import com.imooc.dto.SeckillExecution; 8 | import com.imooc.entity.Seckill; 9 | import com.imooc.entity.SuccessKilled; 10 | import com.imooc.enums.SeckillStateEnum; 11 | import com.imooc.exception.RepeatKillException; 12 | import com.imooc.exception.SeckillCloseException; 13 | import com.imooc.exception.SeckillException; 14 | import com.imooc.client.SeckillService; 15 | import org.apache.commons.collections.MapUtils; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.transaction.annotation.Transactional; 21 | import org.springframework.util.DigestUtils; 22 | 23 | import java.util.Date; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | //@Componet @Service @Dao @Controller 29 | @Service 30 | public class SeckillServiceImpl implements SeckillService { 31 | 32 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 33 | 34 | // 注入Service依赖 35 | @Autowired 36 | private SeckillDao seckillDao; 37 | 38 | @Autowired 39 | private SuccessKilledDao successKilledDao; 40 | 41 | @Autowired 42 | private RedisDao redisDao; 43 | 44 | // md5盐值字符串,用于混淆MD5 45 | private final String slat = "sdfsgsfjks;sf,lasmglksmg"; 46 | 47 | @Override 48 | public List getSeckillList() { 49 | return seckillDao.queryAll(0, 6); 50 | } 51 | 52 | @Override 53 | public Seckill getById(long seckillId) { 54 | return seckillDao.queryById(seckillId); 55 | } 56 | 57 | private String getMD5(long seckillId) { 58 | String base = seckillId + "/" + slat; 59 | String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); 60 | return md5; 61 | } 62 | 63 | @Override 64 | public Exposer exportSeckillUrl(long seckillId) { 65 | // 优化点:缓存优化:超时的基础上维护一致性 66 | // 1.访问redis 67 | Seckill seckill = redisDao.getSeckill(seckillId); 68 | if (seckill == null) { 69 | // 2.访问数据库 70 | seckill = seckillDao.queryById(seckillId); 71 | if (seckill == null) { 72 | return new Exposer(false, seckillId); 73 | } else { 74 | // 3.访问redis 75 | redisDao.putSeckill(seckill); 76 | } 77 | } 78 | if (seckill == null) { 79 | return new Exposer(false, seckillId); 80 | } 81 | Date startTime = seckill.getStartTime(); 82 | Date endTime = seckill.getEndTime(); 83 | // 系统当前时间 84 | Date nowTime = new Date(); 85 | if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) { 86 | return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime()); 87 | } 88 | // 转化特定字符串的过程,不可逆 89 | String md5 = getMD5(seckillId); 90 | return new Exposer(true, md5, seckillId); 91 | } 92 | 93 | @Override 94 | @Transactional 95 | /** 96 | * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格 97 | * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部 98 | * 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制 99 | */ 100 | public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) 101 | throws SeckillException, RepeatKillException, SeckillCloseException { 102 | if (md5 == null || !md5.equals(getMD5(seckillId))) { 103 | throw new SeckillException("seckill data rewrite"); 104 | } 105 | // 执行秒杀逻辑:减库存 + 记录购买行为 106 | Date now = new Date(); 107 | try { 108 | // 记录购买行为 109 | int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone); 110 | // 唯一:seckillId,userPhone 111 | if (insertCount <= 0) { 112 | // 重复秒杀 113 | throw new RepeatKillException("seckill repeated"); 114 | } else { 115 | // 减库存,热点商品竞争 116 | int updateCount = seckillDao.reduceNumber(seckillId, now); 117 | if (updateCount <= 0) { 118 | // 没有更新到记录 rollback 119 | throw new SeckillCloseException("seckill is closed"); 120 | } else { 121 | // 秒杀成功 commit 122 | SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); 123 | return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled); 124 | } 125 | } 126 | } catch (SeckillCloseException e1) { 127 | throw e1; 128 | } catch (RepeatKillException e2) { 129 | throw e2; 130 | } catch (Exception e) { 131 | logger.error(e.getMessage(), e); 132 | // 所有编译期异常转换为运行期异常 133 | throw new SeckillException("seckill inner error:" + e.getMessage()); 134 | } 135 | } 136 | 137 | @Override 138 | public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) { 139 | if (md5 == null || !md5.equals(getMD5(seckillId))) { 140 | return new SeckillExecution(seckillId, SeckillStateEnum.DATA_REWRITE); 141 | } 142 | Date killTime = new Date(); 143 | Map map = new HashMap(); 144 | map.put("seckillId", seckillId); 145 | map.put("phone", userPhone); 146 | map.put("killTime", killTime); 147 | map.put("result", null); 148 | // 执行存储过程,result被赋值 149 | try { 150 | seckillDao.killByProcedure(map); 151 | // 获取result 152 | int result = MapUtils.getInteger(map, "result", -2); 153 | if (result == 1) { 154 | SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); 155 | return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk); 156 | } else { 157 | return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result)); 158 | } 159 | } catch (Exception e) { 160 | logger.error(e.getMessage(), e); 161 | return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR); 162 | } 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/dubbo.proerpties: -------------------------------------------------------------------------------- 1 | dubbo.application.name=seckill-service 2 | dubbo.registry.address=zookeeper://127.0.0.1:2181 3 | dubbo.registry.check=false 4 | dubbo.protocol.name=dubbo -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/jdbc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinbaby/seckill-demo/3c0c39e88ec9dafa36413ddc8b06c10080d58f8c/seckill-service-provider/src/main/resources/jdbc.properties -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/mapper/SeckillDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | UPDATE seckill 10 | SET number = number - 1 11 | WHERE 12 | seckill_id = #{seckillId} 13 | AND start_time #{killTime} 14 | AND end_time >= #{killTime} 15 | AND number > 0 16 | 17 | 18 | 31 | 32 | 47 | 48 | 56 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/mapper/SuccessKilledDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | INSERT ignore INTO success_killed (seckill_id, user_phone, state) 9 | VALUES (#{seckillId}, #{userPhone}, 0) 10 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/spring-dao.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/spring-dubbo-config.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/spring-dubbo-provider.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/spring-service.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/resources/spring/spring-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | seckill-dispatcher 8 | org.springframework.web.servlet.DispatcherServlet 9 | 10 | 11 | contextConfigLocation 12 | classpath:spring/applicationContext.xml 13 | 14 | 15 | 16 | 17 | seckill-dispatcher 18 | 19 | / 20 | 21 | 22 | 23 | contextConfigLocation 24 | classpath:spring/applicationContext.xml 25 | 26 | 27 | org.springframework.web.context.ContextLoaderListener 28 | 29 | -------------------------------------------------------------------------------- /seckill-service-provider/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | 6 | --------------------------------------------------------------------------------