Repository: justinbaby/spring-mybatis Branch: master Commit: 3c0c39e88ec9 Files: 53 Total size: 68.6 KB Directory structure: gitextract_vsg0jjnm/ ├── .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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/maven.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Java CI with Maven on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B package --file pom.xml # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 ================================================ FILE: .gitignore ================================================ *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear *.idea # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 justinbaby Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # seckill(Java高并发秒杀API) 一个spring入门项目,后续将更新spring中级项目 :) #### 所用技术点 - spring - springMVC: MVC框架 - Tomcat: web容器 - mybatis: ORM框架 - bootstrap: css/html框架 - JQuery: JS框架 - Redis: NOSQL数据库 - MySQL: 关系型数据库 - Logback: 日志框架 - JUnit: 单元测试 - CDN: 内容分发服务器 - Procedure:数据库存储过程 - Protostuff:Google开发的基于Java语言的序列化库 - ZooKeeper:分布式应用程序协调服务 - DUBBO:分布式应用服务框架 ## Java高并发秒杀系统API 本项目参考慕课网视频(版权方),并在此基础上进行了模块划分,功能添加,欢迎去官网观看!!!! ## 安装部署 #### 软件环境: - IDEA - MySQL - JDK1.8或以上 - tomcat 8.0 - Redis - Maven #### 硬件环境(最小配置): - CPU:1核 - 内存:1G #### 说明 - seckill-core:秒杀核心模块,部署tomcat启动 - seckill-api:秒杀api模块 - seckill-base:秒杀工具类模块 - seckill-web:秒杀页面模块 #### 步骤 1. 创建数据库,导入初始化脚本initDB.sql 2. 修改系统数据库连接seckill-core/src/main/resources/jdbc.properties 3. Tomcat运行 ## FAQ - Q:为什么我的maven下载依赖jar包这么慢 - A:可以自己下载maven,使用自己的Maven,修改maven安装目录下的/conf/settings.xml,在IDEA或eclipse里配置为默认的设置 ```XML nexus-aliyun central Nexus aliyun http://maven.aliyun.com/nexus/content/groups/public ``` - Q:为什么我的数据库连不上 - A:查看一下jdbc.properties,配置url为你的数据库地址,你的数据库用户名和密码 - Q:为什控制台报Redis异常 - A:查看一下Redis服务器是否启动,没下载的建议去官网下载window或linux版 - Q:eclipse怎么导入 - A:去掉.开头的文件就可以导入!!! ================================================ FILE: docs/script/initDB.sql ================================================ /* Source Server : root Source Server Version : 50538 Source Host : localhost:3306 Source Database : seckill Target Server Type : MYSQL Target Server Version : 50538 File Encoding : 65001 Date: 2017-02-17 12:05:42 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for seckill -- ---------------------------- DROP TABLE IF EXISTS `seckill`; CREATE TABLE `seckill` ( `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存ID', `name` varchar(120) NOT NULL COMMENT '商品名称', `number` int(11) NOT NULL COMMENT '库存数量', `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '秒杀开始时间', `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '秒杀结束时间', `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间', PRIMARY KEY (`seckill_id`), KEY `idx_start_time` (`start_time`), KEY `idx_end_time` (`end_time`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; -- ---------------------------- -- Records of seckill -- ---------------------------- 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'); 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'); 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'); 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'); 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'); 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'); -- ---------------------------- -- Table structure for success_killed -- ---------------------------- DROP TABLE IF EXISTS `success_killed`; CREATE TABLE `success_killed` ( `seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品ID', `user_phone` bigint(20) NOT NULL COMMENT '用户手机号', `state` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`seckill_id`,`user_phone`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'; -- ---------------------------- -- Records of success_killed -- ---------------------------- INSERT INTO `success_killed` VALUES ('1004', '15643645806', '-1', '2017-02-17 12:02:27'); -- ---------------------------- -- View structure for v1 -- ---------------------------- DROP VIEW IF EXISTS `v1`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` VIEW `v1` AS SELECT *FROM seckill ; -- ---------------------------- -- Procedure structure for execute_seckill -- ---------------------------- DROP PROCEDURE IF EXISTS `execute_seckill`; DELIMITER ;; CREATE DEFINER=`root`@`localhost` PROCEDURE `execute_seckill`(IN v_seckill_id bigint, IN v_phone BIGINT, IN v_kill_time TIMESTAMP, OUT r_result INT) BEGIN DECLARE insert_count INT DEFAULT 0; START TRANSACTION; INSERT ignore INTO success_killed (seckill_id, user_phone, create_time) VALUES(v_seckill_id, v_phone, v_kill_time); SELECT ROW_COUNT() INTO insert_count; IF (insert_count = 0) THEN ROLLBACK; SET r_result = -1; ELSEIF (insert_count < 0) THEN ROLLBACK ; SET r_result = -2; ELSE UPDATE seckill SET number = number - 1 WHERE seckill_id = v_seckill_id AND end_time > v_kill_time AND start_time < v_kill_time AND number > 0; SELECT ROW_COUNT() INTO insert_count; IF (insert_count = 0) THEN ROLLBACK; SET r_result = 0; ELSEIF (insert_count < 0) THEN ROLLBACK; SET r_result = -2; ELSE COMMIT; SET r_result = 1; END IF; END IF; END ;; DELIMITER ; SET FOREIGN_KEY_CHECKS=1; ================================================ FILE: pom.xml ================================================ 4.0.0 com.imooc seckill pom 0.0.1-SNAPSHOT seckill-core seckill-model seckill-client seckill Maven Webapp http://maven.apache.org junit junit 4.13.1 ch.qos.logback logback-classic 1.2.0 mysql mysql-connector-java 8.0.16 runtime c3p0 c3p0 0.9.1.2 com.alibaba druid 1.0.13 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 1.3.2 taglibs standard 1.1.2 jstl jstl 1.2 com.fasterxml.jackson.core jackson-databind 2.9.10.7 javax.servlet javax.servlet-api 3.1.0 org.springframework spring-core 4.3.21.RELEASE org.springframework spring-beans 4.1.7.RELEASE org.springframework spring-context 4.1.7.RELEASE org.springframework spring-jdbc 4.1.7.RELEASE org.springframework spring-tx 4.1.7.RELEASE org.springframework spring-web 4.1.7.RELEASE org.springframework spring-webmvc 4.1.7.RELEASE org.springframework spring-test 4.1.7.RELEASE org.springframework spring-aop 4.1.7.RELEASE org.springframework spring-aspects 4.2.0.RELEASE redis.clients jedis 2.7.3 com.dyuproject.protostuff protostuff-core 1.0.8 javax javaee-api 7.0 com.dyuproject.protostuff protostuff-runtime 1.0.8 commons-collections commons-collections 3.2.2 seckill ================================================ FILE: seckill-client/pom.xml ================================================ seckill com.imooc 0.0.1-SNAPSHOT 4.0.0 seckill-client com.imooc seckill-model 0.0.1-SNAPSHOT ================================================ FILE: seckill-client/src/main/java/com/imooc/client/SeckillService.java ================================================ package com.imooc.client; import com.imooc.dto.Exposer; import com.imooc.dto.SeckillExecution; import com.imooc.entity.Seckill; import com.imooc.exception.RepeatKillException; import com.imooc.exception.SeckillCloseException; import com.imooc.exception.SeckillException; import java.util.List; /** * 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常) * * @author yan */ public interface SeckillService { /** * 查询所有秒杀记录 * * @return */ List getSeckillList(); /** * 查询单个秒杀记录 * * @param seckillId * @return */ Seckill getById(long seckillId); /** * 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间 * * @param seckillId * @return */ Exposer exportSeckillUrl(long seckillId); /** * 执行秒杀操作 * * @param seckillId * @param userPhone * @param md5 * @return * @throws SeckillException * @throws RepeatKillException * @throws SeckillCloseException */ SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException; /** * 执行秒杀操作by存储过程 * * @param seckillId * @param userPhone * @param md5 * @return * @throws SeckillException * @throws RepeatKillException * @throws SeckillCloseException */ SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException; } ================================================ FILE: seckill-core/.gitignore ================================================ *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ================================================ FILE: seckill-core/pom.xml ================================================ seckill com.imooc 0.0.1-SNAPSHOT 4.0.0 seckill-core war seckill-dubbo-consumer Maven Webapp http://maven.apache.org org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 com.imooc seckill-model 0.0.1-SNAPSHOT com.imooc seckill-client 0.0.1-SNAPSHOT com.alibaba dubbo 2.5.7 spring org.springframework org.apache.zookeeper zookeeper 3.5.3-beta log4j log4j com.101tec zkclient 0.8 ================================================ FILE: seckill-core/src/main/java/com/imooc/aop/LogAOP.java ================================================ package com.imooc.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; /** * @author yan *

* 采用AOP的方式处理参数问题。 */ @Component @Aspect public class LogAOP { private final Logger LOG = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(* com.imooc.web.*.*(..))") public void aopMethod() { } @Around("aopMethod()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { String classType = joinPoint.getTarget().getClass().getName(); //运用反射的原理创建对象 Class clazz = Class.forName(classType); String clazzName = clazz.getName(); String clazzSimpleName = clazz.getSimpleName(); String methodName = joinPoint.getSignature().getName(); Logger logger = LoggerFactory.getLogger(clazzName); logger.info("clazzName: " + clazzName + ", methodName:" + methodName); long start = System.currentTimeMillis(); LOG.info("before method invoking!"); BindingResult bindingResult = null; System.out.println("---------------"); System.out.println("调用类:" + clazzSimpleName); System.out.println("调用方法:" + methodName); return joinPoint.proceed(); } } ================================================ FILE: seckill-core/src/main/java/com/imooc/web/SeckillController.java ================================================ package com.imooc.web; import com.imooc.dto.SeckillExecution; import com.imooc.exception.SeckillCloseException; import com.imooc.dto.Exposer; import com.imooc.dto.SeckillResult; import com.imooc.entity.Seckill; import com.imooc.enums.SeckillStateEnum; import com.imooc.exception.RepeatKillException; import com.imooc.client.SeckillService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.List; @Controller // @Service @Componet @RequestMapping("/seckill") // url:/模块/资源/{id}/细分 /seckill/list public class SeckillController { //sb private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillService seckillService; //http://localhost:8080/seckill/seckill/list @RequestMapping(value = "/list", method = RequestMethod.GET) public String list(Model model) { // 获取列表页 List list = seckillService.getSeckillList(); model.addAttribute("list", list); // list.jsp + model = ModelAndView return "list";// WEB-INF/jsp/"list".jsp } @RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET) public String detail(@PathVariable("seckillId") Long seckillId, Model model) { if (seckillId == null) { return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null) { return "forward:/seckill/list"; } model.addAttribute("seckill", seckill); return "detail"; } // ajax json @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = { "application/json; charset=utf-8" }) @ResponseBody public SeckillResult exposer(@PathVariable("seckillId") Long seckillId) { SeckillResult result; try { Exposer exposer = seckillService.exportSeckillUrl(seckillId); result = new SeckillResult(true, exposer); } catch (Exception e) { logger.error(e.getMessage(), e); result = new SeckillResult(false, e.getMessage()); } return result; } @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = { "application/json; charset=utf-8" }) @ResponseBody public SeckillResult execute(@PathVariable("seckillId") Long seckillId, @PathVariable("md5") String md5, @CookieValue(value = "killPhone", required = false) Long phone) { // springmvc valid if (phone == null) { return new SeckillResult(false, "未注册"); } try { // 存储过程调用 SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5); return new SeckillResult(true, execution); } catch (RepeatKillException e) { SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL); return new SeckillResult(true, execution); } catch (SeckillCloseException e) { SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END); return new SeckillResult(true, execution); } catch (Exception e) { logger.error(e.getMessage(), e); SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR); return new SeckillResult(true, execution); } } @RequestMapping(value = "/time/now", method = RequestMethod.GET) @ResponseBody public SeckillResult time() { Date now = new Date(); return new SeckillResult(true, now.getTime()); } } ================================================ FILE: seckill-core/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: seckill-core/src/main/resources/spring/applicationContext.xml ================================================ ================================================ FILE: seckill-core/src/main/resources/spring/spring-dubbo-config.xml ================================================ ================================================ FILE: seckill-core/src/main/resources/spring/spring-dubbo-consumer.xml ================================================ ================================================ FILE: seckill-core/src/main/resources/spring/spring-web.xml ================================================ ================================================ FILE: seckill-core/src/main/webapp/WEB-INF/jsp/common/head.jsp ================================================ <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; pageContext.setAttribute("basePath",basePath); %> ================================================ FILE: seckill-core/src/main/webapp/WEB-INF/jsp/common/tag.jsp ================================================ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> ================================================ FILE: seckill-core/src/main/webapp/WEB-INF/jsp/detail.jsp ================================================ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@include file="common/head.jsp"%> 秒杀详情页

${seckill.name}

================================================ FILE: seckill-core/src/main/webapp/WEB-INF/jsp/list.jsp ================================================ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@include file="common/tag.jsp" %> <%@include file="common/head.jsp"%> 秒杀列表页

秒杀列表

名称 库存 开始时间 结束时间 创建时间 详情页
${sk.name} ${sk.number} link
================================================ FILE: seckill-core/src/main/webapp/WEB-INF/web.xml ================================================ seckill-dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring/applicationContext.xml seckill-dispatcher / contextConfigLocation classpath:spring/applicationContext.xml org.springframework.web.context.ContextLoaderListener ================================================ FILE: seckill-core/src/main/webapp/resources/js/seckill.js ================================================ // 存放主要交换逻辑js代码 // javascript 模块化 var seckill = { // 封装秒杀相关ajax的url URL : { basePath : function() { return $('#basePath').val(); }, now : function() { return seckill.URL.basePath() + 'seckill/time/now'; }, exposer : function(seckillId) { return seckill.URL.basePath() + 'seckill/' + seckillId + '/exposer'; }, execution : function(seckillId, md5) { return seckill.URL.basePath() + 'seckill/' + seckillId + '/' + md5 + '/execution'; } }, // 处理秒杀逻辑 handleSeckill : function(seckillId, node) { // 获取秒杀地址,控制显示逻辑,执行秒杀 node.hide().html(''); console.log('exposerUrl=' + seckill.URL.exposer(seckillId));//TODO $.post(seckill.URL.exposer(seckillId), {}, function(result) { // 在回调函数中,执行交互流程 if (result && result['success']) { var exposer = result['data']; if (exposer['exposed']) { // 开启秒杀 var md5 = exposer['md5']; var killUrl = seckill.URL.execution(seckillId, md5); console.log('killUrl=' + killUrl);//TODO $('#killBtn').one('click', function() { // 执行秒杀请求 // 1.先禁用按钮 $(this).addClass('disabled'); // 2.发送秒杀请求 $.post(killUrl, {}, function(result) { if (result && result['success']) { var killResult = result['data']; var state = killResult['state']; var stateInfo = killResult['stateInfo']; // 3.显示秒杀结果 node.html('' + stateInfo + ''); } }); }); node.show(); } else { // 未开启秒杀 var now = exposer['now']; var start = exposer['start']; var end = exposer['end']; // 重新计算计时逻辑 seckill.countdown(seckillId, now, start, end); } } else { console.log('result=' + result); } }); }, // 验证手机号 validatePhone : function(phone) { if (phone && phone.length == 11 && !isNaN(phone)) { return true; } else { return false; } }, // 倒计时 countdown : function(seckillId, nowTime, startTime, endTime) { // 时间判断 var seckillBox = $('#seckillBox'); if (nowTime > endTime) { // 秒杀结束 seckillBox.html('秒杀结束!'); } else if (nowTime < startTime) { // 秒杀未开始,计时事件绑定 var killTime = new Date(startTime + 1000); seckillBox.countdown(killTime, function(event) { // 时间格式 var format = event.strftime('秒杀倒计时:%D天 %H时 %M分 %S秒'); seckillBox.html(format); // 时间完成后回调事件 }).on('finish.countdown', function() { // 获取秒杀地址,控制显示逻辑,执行秒杀 seckill.handleSeckill(seckillId, seckillBox); }); } else { // 秒杀开始 seckill.handleSeckill(seckillId ,seckillBox); } }, // 详情页秒杀逻辑 detail : { // 详情页初始化 init : function(params) { // 用户手机验证和登录,计时交互 // 规划我们的交互流程 // 在cookie中查找手机号 var killPhone = $.cookie('killPhone'); var startTime = params['startTime']; var endTime = params['endTime']; var seckillId = params['seckillId']; // 验证手机号 if (!seckill.validatePhone(killPhone)) { // 绑定phone // 控制输出 var killPhoneModal = $('#killPhoneModal'); killPhoneModal.modal({ show : true,// 显示弹出层 backdrop : 'static',// 禁止位置关闭 keyboard : false // 关闭键盘事件 }) $('#killPhoneBtn').click(function() { var inputPhone = $('#killphoneKey').val(); console.log('inputPhone='+inputPhone);//TODO if (seckill.validatePhone(inputPhone)) { // 电话写入cookie $.cookie('killPhone', inputPhone, { expires : 7, path : '/seckill' }); // 刷新页面 window.location.reload(); } else { $('#killphoneMessage').hide().html('').show(300); } }); } // 已经登录 // 计时交互 var startTime = params['startTime']; var endTime = params['endTime']; var seckillId = params['seckillId']; $.get(seckill.URL.now(), {}, function(result) { if (result && result['success']) { var nowTime = result['data']; // 时间判断,计时交互 seckill.countdown(seckillId, nowTime, startTime, endTime); } else { console.log(result['reult:'] + result); } }); } } } ================================================ FILE: seckill-model/pom.xml ================================================ seckill com.imooc 0.0.1-SNAPSHOT 4.0.0 seckill-model ================================================ FILE: seckill-model/src/main/java/com/imooc/dto/Exposer.java ================================================ package com.imooc.dto; import java.io.Serializable; /** * 暴露秒杀接口DTO * * @author yan */ public class Exposer implements Serializable { // 是否开启秒杀 private boolean exposed; // 一种加密措施 private String md5; // id private long seckillId; // 系统当前时间(毫秒) private long now; // 开启时间 private long start; // 结束时间 private long end; public Exposer(boolean exposed, String md5, long seckillId) { this.exposed = exposed; this.md5 = md5; this.seckillId = seckillId; } public Exposer(boolean exposed, long seckillId, long now, long start, long end) { this.exposed = exposed; this.seckillId = seckillId; this.now = now; this.start = start; this.end = end; } public Exposer(boolean exposed, long seckillId) { this.exposed = exposed; this.seckillId = seckillId; } public boolean isExposed() { return exposed; } public void setExposed(boolean exposed) { this.exposed = exposed; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public long getNow() { return now; } public void setNow(long now) { this.now = now; } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public long getEnd() { return end; } public void setEnd(long end) { this.end = end; } //重写toString方法 @Override public String toString() { return "Exposer [exposed=" + exposed + ", md5=" + md5 + ", seckillId=" + seckillId + ", now=" + now + ", start=" + start + ", end=" + end + "]"; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/dto/SeckillExecution.java ================================================ package com.imooc.dto; import com.imooc.entity.SuccessKilled; import com.imooc.enums.SeckillStateEnum; import java.io.Serializable; /** * 封装秒杀执行后结果 * * @author yan */ public class SeckillExecution implements Serializable { private Long seckillId; // 秒杀执行结果状态 private Integer state; // 状态标识 private String stateInfo; // 秒杀成功对象 private SuccessKilled successKilled; public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum, SuccessKilled successKilled) { this.seckillId = seckillId; this.state = stateEnum.getState(); this.stateInfo = stateEnum.getStateInfo(); this.successKilled = successKilled; } public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum) { this.seckillId = seckillId; if(stateEnum!=null){ this.state = stateEnum.getState(); this.stateInfo = stateEnum.getStateInfo(); } } public Long getSeckillId() { return seckillId; } public void setSeckillId(Long seckillId) { this.seckillId = seckillId; } public Integer getState() { return state; } public void setState(Integer state) { this.state = state; } public String getStateInfo() { return stateInfo; } public void setStateInfo(String stateInfo) { this.stateInfo = stateInfo; } public SuccessKilled getSuccessKilled() { return successKilled; } public void setSuccessKilled(SuccessKilled successKilled) { this.successKilled = successKilled; } @Override public String toString() { return "SeckillExecution [seckillId=" + seckillId + ", state=" + state + ", stateInfo=" + stateInfo + ", successKilled=" + successKilled + "]"; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/dto/SeckillResult.java ================================================ package com.imooc.dto; //封装json结果 public class SeckillResult { private boolean success; private T data; private String error; public SeckillResult(boolean success, String error) { this.success = success; this.error = error; } public SeckillResult(boolean success, T data) { this.success = success; this.data = data; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String getError() { return error; } public void setError(String error) { this.error = error; } @Override public String toString() { return "SeckillResult [success=" + success + ", data=" + data + ", error=" + error + "]"; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/entity/Seckill.java ================================================ package com.imooc.entity; import java.io.Serializable; import java.util.Date; /** * 秒杀库存实体 * * @author yan */ public class Seckill implements Serializable { private long seckillId; private String name; private int number; private Date startTime; private Date endTime; private Date createTime; public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public Date getStartTime() { return startTime; } public void setStartTime(Date startTime) { this.startTime = startTime; } public Date getEndTime() { return endTime; } public void setEndTime(Date endTime) { this.endTime = endTime; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime + ", endTime=" + endTime + ", createTime=" + createTime + "]"; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/entity/SuccessKilled.java ================================================ package com.imooc.entity; import java.io.Serializable; import java.util.Date; /** * 成功秒杀实体 * * @author yan */ public class SuccessKilled implements Serializable { private long seckillId; private long userPhone; private short state; private Date creteTime; // 多对一的复合属性 private Seckill seckill; public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public long getUserPhone() { return userPhone; } public void setUserPhone(long userPhone) { this.userPhone = userPhone; } public short getState() { return state; } public void setState(short state) { this.state = state; } public Date getCreteTime() { return creteTime; } public void setCreteTime(Date creteTime) { this.creteTime = creteTime; } public Seckill getSeckill() { return seckill; } public void setSeckill(Seckill seckill) { this.seckill = seckill; } @Override public String toString() { return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state + ", creteTime=" + creteTime + "]"; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/enums/SeckillStateEnum.java ================================================ package com.imooc.enums; /** * 使用枚举表述常量数据字典 * * @author yan */ public enum SeckillStateEnum { SUCCESS(1, "秒杀成功"), END(0, "秒杀结束"), REPEAT_KILL(-1, "重复秒杀"), INNER_ERROR(-2, "系统异常"), DATA_REWRITE(-3, "数据篡改"); private Integer state; private String stateInfo; private SeckillStateEnum(int state, String stateInfo) { this.state = state; this.stateInfo = stateInfo; } public Integer getState() { return state; } public String getStateInfo() { return stateInfo; } public static SeckillStateEnum stateOf(Integer index) { for (SeckillStateEnum state : values()) { if (state.getState().equals(index)) { return state; } } return null; } } ================================================ FILE: seckill-model/src/main/java/com/imooc/exception/RepeatKillException.java ================================================ package com.imooc.exception; /** * 重复秒杀异常(运行期异常) * * @author yan */ public class RepeatKillException extends SeckillException { public RepeatKillException(String message) { super(message); } public RepeatKillException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: seckill-model/src/main/java/com/imooc/exception/SeckillCloseException.java ================================================ package com.imooc.exception; /** * 秒杀关闭异常 * * @author yan */ public class SeckillCloseException extends SeckillException { public SeckillCloseException(String message) { super(message); } public SeckillCloseException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: seckill-model/src/main/java/com/imooc/exception/SeckillException.java ================================================ package com.imooc.exception; /** * 秒杀相关业务异常 * * @author yan */ public class SeckillException extends RuntimeException { public SeckillException(String message) { super(message); } public SeckillException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: seckill-service-provider/pom.xml ================================================ seckill com.imooc 0.0.1-SNAPSHOT 4.0.0 seckill-service war seckill-dubbo-consumer Maven Webapp http://maven.apache.org org.apache.maven.plugins maven-compiler-plugin 1.6 1.6 com.imooc seckill-model 0.0.1-SNAPSHOT com.imooc seckill-client 0.0.1-SNAPSHOT com.alibaba dubbo 2.5.7 spring org.springframework org.apache.zookeeper zookeeper 3.5.3-beta log4j log4j com.101tec zkclient 0.8 ================================================ FILE: seckill-service-provider/src/main/java/com/imooc/dao/SeckillDao.java ================================================ package com.imooc.dao; import com.imooc.entity.Seckill; import org.apache.ibatis.annotations.Param; import java.util.Date; import java.util.List; import java.util.Map; /** * 秒杀库存DAO接口 * * @author yan */ public interface SeckillDao { /** * 减库存 * * @param seckillId * @param killTime * @return 如果影响行数等于>1,表示更新的记录行数 */ int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); /** * 根据id查询秒杀对象 * * @param seckillId * @return */ Seckill queryById(long seckillId); /** * 根据偏移量查询秒杀商品列表 * * @param offset * @param limit * @return */ List queryAll(@Param("offset") int offset, @Param("limit") int limit); /** * 使用存储过程执行秒杀 * * @param paramMap */ void killByProcedure(Map paramMap); } ================================================ FILE: seckill-service-provider/src/main/java/com/imooc/dao/SuccessKilledDao.java ================================================ package com.imooc.dao; import com.imooc.entity.SuccessKilled; import org.apache.ibatis.annotations.Param; public interface SuccessKilledDao { /** * 插入购买明细,可过滤重复 * * @param seckillId * @param userPhone * @return 插入的行数 */ int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); /** * 根据id查询SuccessKilled并携带秒杀产品对象实体 * * @param seckillId * @param userPhone * @return */ SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); } ================================================ FILE: seckill-service-provider/src/main/java/com/imooc/dao/cache/RedisDao.java ================================================ package com.imooc.dao.cache; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.runtime.RuntimeSchema; import com.imooc.entity.Seckill; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class RedisDao { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JedisPool jedisPool; private RuntimeSchema schema = RuntimeSchema.createFrom(Seckill.class); public RedisDao(String ip, int port) { jedisPool = new JedisPool(ip, port); } public Seckill getSeckill(long seckillId) { // redis操作逻辑 try { Jedis jedis = jedisPool.getResource(); try { String key = "seckill:" + seckillId; // 并没有实现内部序列化操作 // get -> byte[] -> 反序列化 -> object[Seckill] // 采用自定义序列化 // protostuff : pojo. byte[] bytes = jedis.get(key.getBytes()); if (bytes != null) { Seckill seckill = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes, seckill, schema); // seckill被反序列化 return seckill; } } finally { jedis.close(); } } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } public String putSeckill(Seckill seckill) { // set Object(Seckill) -> 序列号 -> byte[] try { Jedis jedis = jedisPool.getResource(); try { String key = "seckill:" + seckill.getSeckillId(); byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); // 超时缓存 int timeout = 60 * 60; String result = jedis.setex(key.getBytes(), timeout, bytes); return result; } finally { jedis.close(); } } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } } ================================================ FILE: seckill-service-provider/src/main/java/com/imooc/service/impl/App.java ================================================ package com.imooc.service.impl; import com.imooc.client.SeckillService; import com.imooc.entity.Seckill; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; import java.util.List; public class App { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/applicationContext.xml"}); context.start(); SeckillService seckillService = context.getBean("seckillService",SeckillService.class); List getSeckillList = seckillService.getSeckillList(); System.out.println(getSeckillList); System.out.println("press any key to exit."); System.in.read(); } } ================================================ FILE: seckill-service-provider/src/main/java/com/imooc/service/impl/SeckillServiceImpl.java ================================================ package com.imooc.service.impl; import com.imooc.dao.SeckillDao; import com.imooc.dao.SuccessKilledDao; import com.imooc.dao.cache.RedisDao; import com.imooc.dto.Exposer; import com.imooc.dto.SeckillExecution; import com.imooc.entity.Seckill; import com.imooc.entity.SuccessKilled; import com.imooc.enums.SeckillStateEnum; import com.imooc.exception.RepeatKillException; import com.imooc.exception.SeckillCloseException; import com.imooc.exception.SeckillException; import com.imooc.client.SeckillService; import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; //@Componet @Service @Dao @Controller @Service public class SeckillServiceImpl implements SeckillService { private Logger logger = LoggerFactory.getLogger(this.getClass()); // 注入Service依赖 @Autowired private SeckillDao seckillDao; @Autowired private SuccessKilledDao successKilledDao; @Autowired private RedisDao redisDao; // md5盐值字符串,用于混淆MD5 private final String slat = "sdfsgsfjks;sf,lasmglksmg"; @Override public List getSeckillList() { return seckillDao.queryAll(0, 6); } @Override public Seckill getById(long seckillId) { return seckillDao.queryById(seckillId); } private String getMD5(long seckillId) { String base = seckillId + "/" + slat; String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } @Override public Exposer exportSeckillUrl(long seckillId) { // 优化点:缓存优化:超时的基础上维护一致性 // 1.访问redis Seckill seckill = redisDao.getSeckill(seckillId); if (seckill == null) { // 2.访问数据库 seckill = seckillDao.queryById(seckillId); if (seckill == null) { return new Exposer(false, seckillId); } else { // 3.访问redis redisDao.putSeckill(seckill); } } if (seckill == null) { return new Exposer(false, seckillId); } Date startTime = seckill.getStartTime(); Date endTime = seckill.getEndTime(); // 系统当前时间 Date nowTime = new Date(); if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) { return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime()); } // 转化特定字符串的过程,不可逆 String md5 = getMD5(seckillId); return new Exposer(true, md5, seckillId); } @Override @Transactional /** * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格 * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部 * 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制 */ public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException { if (md5 == null || !md5.equals(getMD5(seckillId))) { throw new SeckillException("seckill data rewrite"); } // 执行秒杀逻辑:减库存 + 记录购买行为 Date now = new Date(); try { // 记录购买行为 int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone); // 唯一:seckillId,userPhone if (insertCount <= 0) { // 重复秒杀 throw new RepeatKillException("seckill repeated"); } else { // 减库存,热点商品竞争 int updateCount = seckillDao.reduceNumber(seckillId, now); if (updateCount <= 0) { // 没有更新到记录 rollback throw new SeckillCloseException("seckill is closed"); } else { // 秒杀成功 commit SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled); } } } catch (SeckillCloseException e1) { throw e1; } catch (RepeatKillException e2) { throw e2; } catch (Exception e) { logger.error(e.getMessage(), e); // 所有编译期异常转换为运行期异常 throw new SeckillException("seckill inner error:" + e.getMessage()); } } @Override public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) { if (md5 == null || !md5.equals(getMD5(seckillId))) { return new SeckillExecution(seckillId, SeckillStateEnum.DATA_REWRITE); } Date killTime = new Date(); Map map = new HashMap(); map.put("seckillId", seckillId); map.put("phone", userPhone); map.put("killTime", killTime); map.put("result", null); // 执行存储过程,result被赋值 try { seckillDao.killByProcedure(map); // 获取result int result = MapUtils.getInteger(map, "result", -2); if (result == 1) { SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk); } else { return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result)); } } catch (Exception e) { logger.error(e.getMessage(), e); return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR); } } } ================================================ FILE: seckill-service-provider/src/main/resources/dubbo.proerpties ================================================ dubbo.application.name=seckill-service dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.registry.check=false dubbo.protocol.name=dubbo ================================================ FILE: seckill-service-provider/src/main/resources/jdbc.properties ================================================ jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=root #ʼӴС jdbc.initialSize=0 #ӳ jdbc.maxActive=20 #ӳС jdbc.minIdle=1 #ȡȴʱ jdbc.maxWait=60000 ================================================ FILE: seckill-service-provider/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: seckill-service-provider/src/main/resources/mapper/SeckillDao.xml ================================================ UPDATE seckill SET number = number - 1 WHERE seckill_id = #{seckillId} AND start_time #{killTime} AND end_time >= #{killTime} AND number > 0 ================================================ FILE: seckill-service-provider/src/main/resources/mapper/SuccessKilledDao.xml ================================================ INSERT ignore INTO success_killed (seckill_id, user_phone, state) VALUES (#{seckillId}, #{userPhone}, 0) ================================================ FILE: seckill-service-provider/src/main/resources/mybatis-config.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/applicationContext.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/spring-dao.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/spring-dubbo-config.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/spring-dubbo-provider.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/spring-service.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/resources/spring/spring-web.xml ================================================ ================================================ FILE: seckill-service-provider/src/main/webapp/WEB-INF/web.xml ================================================ seckill-dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring/applicationContext.xml seckill-dispatcher / contextConfigLocation classpath:spring/applicationContext.xml org.springframework.web.context.ContextLoaderListener ================================================ FILE: seckill-service-provider/src/main/webapp/index.jsp ================================================

Hello World!