└── mySecondKill ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── jinsong │ │ ├── MySecondKillApplication.java │ │ ├── controller │ │ ├── SeckillController.java │ │ └── TestController.java │ │ ├── dao │ │ ├── RedisDAO.java │ │ ├── SeckillDAO.java │ │ └── SuccessKilledDAO.java │ │ ├── dto │ │ ├── Exposer.java │ │ ├── SeckillExecution.java │ │ └── SeckillResult.java │ │ ├── enums │ │ └── SeckillStateEnum.java │ │ ├── exception │ │ ├── RepeatKillException.java │ │ ├── SeckillCloseException.java │ │ └── SeckillException.java │ │ ├── model │ │ ├── Seckill.java │ │ └── SuccessKilled.java │ │ └── service │ │ ├── SeckillService.java │ │ └── impl │ │ └── SeckillServiceImpl.java └── resources │ ├── application.properties │ ├── mapper │ ├── SeckillDAO.xml │ └── SuccessKilledDAO.xml │ ├── mybatis-config.xml │ ├── sql │ └── schema.sql │ ├── static │ └── scripts │ │ └── seckill.js │ └── templates │ ├── detail.html │ └── list.html └── test └── java └── com └── jinsong ├── MySecondKillApplicationTests.java ├── dao ├── SeckillDAOTest.java └── SuccessKilledDAOTest.java └── service └── impl └── SeckillServiceImplTest.java /mySecondKill/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /mySecondKill/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js3560750/mySecondKill/f6b62dd8ea124c85896d7e99969535fda5bc37c1/mySecondKill/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /mySecondKill/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /mySecondKill/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mySecondKill/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /mySecondKill/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.jinsong 7 | mySecondKill 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | mySecondKill 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.6.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR2 26 | 27 | 3.0.2.RELEASE 28 | 2.0.4 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-thymeleaf 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-starter 43 | 44 | 45 | org.mybatis.spring.boot 46 | mybatis-spring-boot-starter 47 | 1.3.0 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-web 52 | 53 | 54 | 55 | mysql 56 | mysql-connector-java 57 | runtime 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-test 62 | test 63 | 64 | 65 | 66 | redis.clients 67 | jedis 68 | 69 | 70 | 71 | 72 | io.protostuff 73 | protostuff-api 74 | 75 | 76 | io.protostuff 77 | protostuff-core 78 | 79 | 80 | io.protostuff 81 | protostuff-runtime 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.springframework.cloud 89 | spring-cloud-dependencies 90 | ${spring-cloud.version} 91 | pom 92 | import 93 | 94 | 95 | io.protostuff 96 | protostuff-bom 97 | 1.3.5 98 | pom 99 | import 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.springframework.boot 108 | spring-boot-maven-plugin 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/MySecondKillApplication.java: -------------------------------------------------------------------------------- 1 | package com.jinsong; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.support.SpringBootServletInitializer; 6 | 7 | /** 8 | * 部署到服务器上时,入口要继承SpringBootServletInitializer 9 | * 并且整个项目只能有这里一个main函数 10 | * 11 | * pom.xml中改为war 12 | * 13 | * cmd进入项目根目录运行以下命令打War包 14 | * mvn package -Dmaven.test.skip=true 15 | * @author 188949420@qq.com 16 | * 17 | */ 18 | @SpringBootApplication 19 | public class MySecondKillApplication extends SpringBootServletInitializer { 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(MySecondKillApplication.class, args); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/controller/SeckillController.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.controller; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.CookieValue; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | import com.jinsong.dto.Exposer; 16 | import com.jinsong.dto.SeckillExecution; 17 | import com.jinsong.dto.SeckillResult; 18 | import com.jinsong.enums.SeckillStateEnum; 19 | import com.jinsong.exception.RepeatKillException; 20 | import com.jinsong.exception.SeckillCloseException; 21 | import com.jinsong.exception.SeckillException; 22 | import com.jinsong.model.Seckill; 23 | import com.jinsong.service.SeckillService; 24 | 25 | @Controller //这里没有加总的映射地址 26 | public class SeckillController { 27 | 28 | // 直接调用Service,不要调用Service的实现ServiceImpl 29 | @Autowired 30 | SeckillService seckillService; 31 | 32 | /** 33 | * 获取列表页 34 | * 35 | * @param model 36 | * @return 37 | */ 38 | @RequestMapping(value = "/list", method = RequestMethod.GET) 39 | public String list(Model model) { 40 | List seckillList = seckillService.getSeckillList(); 41 | 42 | // model里的内容作为JSON串会自动的给到前端页面里去 43 | model.addAttribute("list", seckillList); 44 | 45 | return "list"; 46 | } 47 | 48 | /** 49 | * 点击列表页中的商品,跳转到详情页 50 | * 51 | * @return 52 | */ 53 | @RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET) 54 | public String detail(@PathVariable("seckillId") Long seckillId, // 这里用Long 没有用long ,因为只有Long型才能判断是否==null 55 | Model model) { 56 | 57 | if (seckillId == null) { 58 | // 如果传入的商品ID为空,则跳转到list页 59 | return "redirect:/seckillId/list"; 60 | } 61 | 62 | Seckill seckill = seckillService.getById(seckillId); 63 | if (seckill == null) { 64 | // 如果传入的商品ID,而数据库中没有该ID,则跳转到List页 65 | return "forward:/seckillId/list"; 66 | } 67 | model.addAttribute("seckill", seckill); 68 | 69 | return "detail"; 70 | } 71 | 72 | /** 73 | * 获取系统时间 74 | * 75 | */ 76 | @RequestMapping(value = "/time/now", method = RequestMethod.GET) 77 | @ResponseBody // 返回的不是页面,使return的结果为JSON数据 78 | public SeckillResult time() { 79 | 80 | Date nowTime = new Date(); 81 | // 泛型指定nowTime.getTime()是个Long型的对象 82 | return new SeckillResult(true, nowTime.getTime()); 83 | } 84 | 85 | /** 86 | * ajax,JSON,暴露秒杀接口,何时调用这个链接是写在seckill.js文件里的 87 | */ 88 | @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.GET, produces = { 89 | "application/json;charset=UTF-8" }) // produces指定返回结果是一个JSON 90 | @ResponseBody //使return的结果为JSON数据 91 | public SeckillResult exposer(@PathVariable("seckillId") long seckillId) { 92 | 93 | SeckillResult result; 94 | 95 | try { 96 | // 在秒杀开启时输出秒杀接口的地址 ,秒杀未开启则输出系统时间和秒杀时间 97 | Exposer exposer = seckillService.exportSeckillUrl(seckillId); 98 | result = new SeckillResult(true, exposer); 99 | } catch (Exception e) { 100 | e.printStackTrace(); 101 | result = new SeckillResult(false, e.getMessage()); 102 | } 103 | 104 | return result; 105 | 106 | } 107 | 108 | /** 109 | * 执行秒杀,何时调用这个链接是写在seckill.js文件里的 110 | */ 111 | @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = { 112 | "application/json;charset=UTF-8" }) 113 | @ResponseBody //使return的结果为JSON数据 114 | public SeckillResult execute(@PathVariable("seckillId") long seckillId, 115 | @PathVariable("md5") String md5, @CookieValue(value = "userPhone", required = false) Long userPhone) { 116 | 117 | // 如果用户未登录、未注册就执行秒杀 118 | if (userPhone == null) { 119 | return new SeckillResult(false, "用户未注册"); 120 | } 121 | 122 | try { 123 | // 正常执行秒杀 124 | SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, userPhone, md5); 125 | return new SeckillResult(true, seckillExecution); 126 | 127 | } catch (RepeatKillException e) { 128 | // seckillService.executeSeckill执行过程中抛出重复秒杀异常 129 | SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL); 130 | return new SeckillResult(true, seckillExecution); 131 | 132 | } catch (SeckillCloseException e) { 133 | // seckillService.executeSeckill执行过程中抛出关闭秒杀异常 134 | SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.END); 135 | return new SeckillResult(true, seckillExecution); 136 | 137 | } catch (SeckillException e) { 138 | // seckillService.executeSeckill执行过程中抛出其他秒杀异常 139 | SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR); 140 | return new SeckillResult(true, seckillExecution); 141 | } 142 | 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import com.jinsong.dao.SeckillDAO; 8 | import com.jinsong.model.Seckill; 9 | 10 | @RestController 11 | public class TestController { 12 | 13 | @Autowired 14 | private SeckillDAO seckillDAO; 15 | 16 | @GetMapping(value = "/test") 17 | public String myTest() { 18 | Seckill seckill = seckillDAO.queryById(1000); 19 | return seckill.toString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dao/RedisDAO.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.stereotype.Service; 6 | 7 | import com.jinsong.model.Seckill; 8 | 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisPool; 11 | import io.protostuff.LinkedBuffer; 12 | import io.protostuff.ProtostuffIOUtil; 13 | import io.protostuff.runtime.RuntimeSchema; 14 | 15 | /** 16 | * 通过Jedis接口使用Redis数据库 17 | * 18 | * 注意:往Redis中放的对象一定要序列化之后再放入,参考http://www.cnblogs.com/yaobolove/p/5632891.html 19 | * 序列化的目的是将一个实现了Serializable接口的对象转换成一个字节序列,可以。 把该字节序列保存起来(例如:保存在一个文件里), 20 | * 以后可以随时将该字节序列恢复为原来的对象。 21 | * 22 | * Redis 缓存对象时需要将其序列化,而何为序列化,实际上就是将对象以字节形式存储。这样,不管对象的属性是字符串、整型还是图片、视频等二进制类型, 23 | * 都可以将其保存在字节数组中。对象序列化后便可以持久化保存或网络传输。需要还原对象时,只需将字节数组再反序列化即可。 24 | * 25 | * 因为要在项目中用到,所以要添加@Service,把这个做成一个服务 26 | * 27 | * 因为要初始化连接池JedisPool,所以要implements InitializingBean并调用默认的 28 | * afterPropertiesSet()方法 29 | * 30 | * @author 18894 31 | * 32 | */ 33 | @Service 34 | public class RedisDAO implements InitializingBean { 35 | 36 | // 连接池 37 | private JedisPool jedisPool; 38 | 39 | // protostuff序列化工具用到的架构 40 | // 对于对象,可以用RuntimeSchema来生成schema(架构)通过反射在运行时缓存和使用 41 | private RuntimeSchema schema = RuntimeSchema.createFrom(Seckill.class); 42 | 43 | @Override 44 | public void afterPropertiesSet() throws Exception { 45 | // TODO Auto-generated method stub 46 | // 初始化连接池,连接Redis中第8个数据库,Redis端口是6379,默认配置Redis最多有16个数据库 47 | jedisPool = new JedisPool("redis://localhost:6379/8"); 48 | 49 | } 50 | 51 | // 将seckill对象序列化后存入Redis 52 | public String setSeckill(Seckill seckill) { 53 | try { 54 | Jedis jedis = jedisPool.getResource(); 55 | try { 56 | String key = "seckill:" + seckill.getSeckillId(); 57 | // protostuff工具 58 | // 将seckill对象序列化成字节数组 59 | byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema, 60 | LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); 61 | // 超时缓存 62 | int timeout = 60 * 60; // 1小时 63 | // 设置key对应的字符串value,并给一个超期时间 64 | String result = jedis.setex(key.getBytes(), timeout, bytes); 65 | 66 | return result; 67 | 68 | } finally { 69 | jedis.close(); 70 | } 71 | } catch (Exception e) { 72 | System.out.println(e.getMessage()); 73 | } 74 | return null; 75 | } 76 | 77 | // 从Redis中得到seckill对象(反序列化之后) 78 | public Seckill getSeckill(long seckillId) { 79 | try { 80 | Jedis jedis = jedisPool.getResource(); 81 | try { 82 | String key = "seckill:" + seckillId; 83 | byte[] bytes = jedis.get(key.getBytes()); 84 | // 从Redis缓存中反序列化后获取seckill对象 85 | if (bytes != null) { 86 | Seckill seckill = schema.newMessage(); 87 | // 反序列化 88 | ProtostuffIOUtil.mergeFrom(bytes, seckill, schema); 89 | // 返回反序列化之后的seckill对象 90 | return seckill; 91 | } 92 | } finally { 93 | jedis.close(); 94 | } 95 | } catch (Exception e) { 96 | // TODO: handle exception 97 | } 98 | return null; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dao/SeckillDAO.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dao; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import com.jinsong.model.Seckill; 10 | 11 | @Mapper 12 | public interface SeckillDAO { 13 | 14 | /** 15 | * 减库存 16 | * 17 | * @param seckillId 18 | * @param killTime 19 | * @return 如果返回值>1,表示更新库存的记录行数,返回值=0,表示更新失败 20 | */ 21 | int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); 22 | 23 | /** 24 | * 根据id查询秒杀的商品信息 25 | * 26 | * @param seckillId 27 | * @return 28 | */ 29 | Seckill queryById(long seckillId); 30 | 31 | /** 32 | * 根据偏移量查询秒杀商品列表 33 | * 34 | * @param offset 35 | * @param limit 36 | * @return 37 | */ 38 | List queryAll(@Param("offset") int offset, @Param("limit") int limit); 39 | } 40 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dao/SuccessKilledDAO.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dao; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import com.jinsong.model.SuccessKilled; 7 | 8 | @Mapper 9 | public interface SuccessKilledDAO { 10 | 11 | /** 12 | * 插入购买明细,可过滤重复 13 | * 14 | * @param seckillId 15 | * @param userPhone 16 | * @return 插入的行数,有主键冲突(重复秒杀)时返回0 17 | */ 18 | int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); 19 | 20 | /** 21 | * 根据秒杀商品的id查询明细SuccessKilled对象(该对象携带了Seckill秒杀产品对象) 22 | * 23 | * @param seckillId 24 | * @return 25 | */ 26 | SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); 27 | } 28 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dto/Exposer.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dto; 2 | 3 | /** 4 | * DTO层,相当于我们自定义的数据返回类型 5 | * 6 | * Created by codingBoy on 16/11/27. 7 | * 暴露秒杀地址(接口)DTO 8 | */ 9 | public class Exposer { 10 | 11 | //是否开启秒杀 12 | private boolean exposed; 13 | 14 | //加密措施 15 | private String md5; 16 | 17 | //秒杀商品ID 18 | private long seckillId; 19 | 20 | //系统当前时间(毫秒) 21 | private long now; 22 | 23 | //秒杀的开启时间 24 | private long start; 25 | 26 | //秒杀的结束时间 27 | private long end; 28 | 29 | //构造方法:秒杀未开启,不暴露接口 30 | public Exposer(boolean exposed, long seckillId, long now, long start, long end) { 31 | super(); 32 | this.exposed = exposed; 33 | this.seckillId = seckillId; 34 | this.now = now; 35 | this.start = start; 36 | this.end = end; 37 | } 38 | 39 | //构造方法:秒杀商品不存在,不暴露接口 40 | public Exposer(boolean exposed, long seckillId) { 41 | super(); 42 | this.exposed = exposed; 43 | this.seckillId = seckillId; 44 | } 45 | 46 | //构造方法:秒杀开启,暴露接口,MD5是商品ID经过加密后的值 47 | public Exposer(boolean exposed, String md5, long seckillId) { 48 | super(); 49 | this.exposed = exposed; 50 | this.md5 = md5; 51 | this.seckillId = seckillId; 52 | } 53 | 54 | public boolean isExposed() { 55 | return exposed; 56 | } 57 | 58 | public void setExposed(boolean exposed) { 59 | this.exposed = exposed; 60 | } 61 | 62 | public String getMd5() { 63 | return md5; 64 | } 65 | 66 | public void setMd5(String md5) { 67 | this.md5 = md5; 68 | } 69 | 70 | public long getSeckillId() { 71 | return seckillId; 72 | } 73 | 74 | public void setSeckillId(long seckillId) { 75 | this.seckillId = seckillId; 76 | } 77 | 78 | public long getNow() { 79 | return now; 80 | } 81 | 82 | public void setNow(long now) { 83 | this.now = now; 84 | } 85 | 86 | public long getStart() { 87 | return start; 88 | } 89 | 90 | public void setStart(long start) { 91 | this.start = start; 92 | } 93 | 94 | public long getEnd() { 95 | return end; 96 | } 97 | 98 | public void setEnd(long end) { 99 | this.end = end; 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "Exposer [exposed=" + exposed + ", md5=" + md5 + ", seckillId=" + seckillId + ", now=" + now + ", start=" 105 | + start + ", end=" + end + "]"; 106 | } 107 | 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dto/SeckillExecution.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dto; 2 | 3 | import com.jinsong.enums.SeckillStateEnum; 4 | import com.jinsong.model.SuccessKilled; 5 | 6 | /** 7 | * DTO层,相当于我们自定义的数据返回类型 8 | * 9 | * 封装执行秒杀后的结果:是否秒杀成功 Created by codingBoy on 16/11/27. 10 | */ 11 | public class SeckillExecution { 12 | 13 | //秒杀商品ID 14 | private long seckillId; 15 | 16 | // 秒杀执行结果的状态 17 | private int state; 18 | 19 | // 状态的明文标识 20 | private String stateInfo; 21 | 22 | // 当秒杀成功时,需要传递秒杀成功的对象回去 23 | private SuccessKilled successKilled; 24 | 25 | //构造方法:秒杀成功返回的信息 26 | public SeckillExecution(long seckillId, SeckillStateEnum state, SuccessKilled successKilled) { 27 | super(); 28 | this.seckillId = seckillId; 29 | this.state = state.getState(); 30 | this.stateInfo = state.getInfo(); 31 | this.successKilled = successKilled; 32 | } 33 | 34 | //构造方法:秒杀失败返回的信息 35 | public SeckillExecution(long seckillId, SeckillStateEnum state) { 36 | super(); 37 | this.seckillId = seckillId; 38 | this.state = state.getState(); 39 | this.stateInfo = state.getInfo(); 40 | } 41 | 42 | public long getSeckillId() { 43 | return seckillId; 44 | } 45 | 46 | public void setSeckillId(long seckillId) { 47 | this.seckillId = seckillId; 48 | } 49 | 50 | public int getState() { 51 | return state; 52 | } 53 | 54 | public void setState(int state) { 55 | this.state = state; 56 | } 57 | 58 | public String getStateInfo() { 59 | return stateInfo; 60 | } 61 | 62 | public void setStateInfo(String stateInfo) { 63 | this.stateInfo = stateInfo; 64 | } 65 | 66 | public SuccessKilled getSuccessKilled() { 67 | return successKilled; 68 | } 69 | 70 | public void setSuccessKilled(SuccessKilled successKilled) { 71 | this.successKilled = successKilled; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "SeckillExecution [seckillId=" + seckillId + ", state=" + state + ", stateInfo=" + stateInfo 77 | + ", successKilled=" + successKilled + "]"; 78 | } 79 | 80 | 81 | 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/dto/SeckillResult.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dto; 2 | 3 | /** 4 | * Created by codingBoy on 16/11/28. 5 | */ 6 | //将所有的ajax请求返回类型,全部封装成json数据 7 | public class SeckillResult { 8 | 9 | //秒杀执行是否成功 10 | private boolean success; 11 | //秒杀执行的数据 12 | private T data; 13 | //秒杀执行错误时的错误信息 14 | private String error; 15 | 16 | //构造器:秒杀执行成功 17 | public SeckillResult(boolean success, T data) { 18 | super(); 19 | this.success = success; 20 | this.data = data; 21 | } 22 | 23 | //构造器:秒杀执行失败 24 | public SeckillResult(boolean success, String error) { 25 | super(); 26 | this.success = success; 27 | this.error = error; 28 | } 29 | 30 | public boolean isSuccess() { 31 | return success; 32 | } 33 | 34 | public void setSuccess(boolean success) { 35 | this.success = success; 36 | } 37 | 38 | public T getData() { 39 | return data; 40 | } 41 | 42 | public void setData(T data) { 43 | this.data = data; 44 | } 45 | 46 | public String getError() { 47 | return error; 48 | } 49 | 50 | public void setError(String error) { 51 | this.error = error; 52 | } 53 | 54 | 55 | 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/enums/SeckillStateEnum.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.enums; 2 | 3 | /** 4 | * 使用枚举表示常量数据字典 5 | * 6 | * @author 188949420@qq.com 7 | * 8 | */ 9 | public enum SeckillStateEnum { 10 | 11 | SUCCESS(1,"秒杀成功"), 12 | END(0,"秒杀结束"), 13 | REPEAT_KILL(-1,"重复秒杀"), 14 | INNER_ERROR(-2,"系统异常"), 15 | DATE_REWRITE(-3,"数据篡改"); 16 | 17 | private int state; 18 | private String info; 19 | 20 | //添加这个构造器,上面的代码就不报错了 21 | private SeckillStateEnum(int state, String info) { 22 | this.state = state; 23 | this.info = info; 24 | } 25 | 26 | //根据index也就是上面的-1、0、1拿到对应的值 27 | public static SeckillStateEnum stateOf(int index) { 28 | for(SeckillStateEnum state : values()) { //枚举内部有一个values方法拿到所有数据 29 | if(state.getState()==index) { //如果传入的index属于我们这里定义的-3到1之间的一个,则返回这个枚举 30 | 31 | return state; 32 | } 33 | 34 | } 35 | return null; 36 | } 37 | 38 | public int getState() { 39 | return state; 40 | } 41 | 42 | public void setState(int state) { 43 | this.state = state; 44 | } 45 | 46 | public String getInfo() { 47 | return info; 48 | } 49 | 50 | public void setInfo(String info) { 51 | this.info = info; 52 | } 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/exception/RepeatKillException.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.exception; 2 | 3 | /** 4 | * 重复秒杀异常,是一个运行期异常,不需要我们手动try catch 5 | * Mysql只支持运行期异常的回滚操作 6 | * Created by codingBoy on 16/11/27. 7 | */ 8 | public class RepeatKillException extends SeckillException{ 9 | 10 | //构造方法:这个异常抛出的信息由我们定义 11 | public RepeatKillException(String message) { 12 | super(message); 13 | } 14 | 15 | public RepeatKillException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/exception/SeckillCloseException.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.exception; 2 | 3 | /** 4 | * 秒杀关闭异常,当秒杀结束时用户还要进行秒杀就会出现这个异常 Created by codingBoy on 16/11/27. 5 | */ 6 | public class SeckillCloseException extends SeckillException { 7 | 8 | // 构造方法:这个异常抛出的信息由我们定义 9 | public SeckillCloseException(String message) { 10 | super(message); 11 | } 12 | 13 | public SeckillCloseException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/exception/SeckillException.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.exception; 2 | 3 | /** 4 | * 秒杀相关的所有业务异常 Created by codingBoy on 16/11/27. 5 | */ 6 | public class SeckillException extends RuntimeException { 7 | 8 | //构造方法:这个异常抛出的信息由我们定义 9 | public SeckillException(String message) { 10 | super(message); 11 | } 12 | 13 | public SeckillException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/model/Seckill.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * 数据库mysecondkil 中的seckill表模型 7 | * 8 | * @author 188949420@qq.com 9 | * 10 | */ 11 | public class Seckill { 12 | 13 | private long seckillId; 14 | private String name; 15 | private int number; 16 | private Date startTime; 17 | private Date endTime; 18 | private Date createTime; 19 | 20 | public long getSeckillId() { 21 | return seckillId; 22 | } 23 | 24 | public void setSeckillId(long seckillId) { 25 | this.seckillId = seckillId; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public int getNumber() { 37 | return number; 38 | } 39 | 40 | public void setNumber(int number) { 41 | this.number = number; 42 | } 43 | 44 | public Date getStartTime() { 45 | return startTime; 46 | } 47 | 48 | public void setStartTime(Date startTime) { 49 | this.startTime = startTime; 50 | } 51 | 52 | public Date getEndTime() { 53 | return endTime; 54 | } 55 | 56 | public void setEndTime(Date endTime) { 57 | this.endTime = endTime; 58 | } 59 | 60 | public Date getCreateTime() { 61 | return createTime; 62 | } 63 | 64 | public void setCreateTime(Date createTime) { 65 | this.createTime = createTime; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime 71 | + ", endTime=" + endTime + ", createTime=" + createTime + "]"; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/model/SuccessKilled.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * 数据库mysecondkill 7 | * 中的success_killed表模型 8 | * @author 188949420@qq.com 9 | * 10 | */ 11 | public class SuccessKilled { 12 | 13 | private long seckillId; 14 | private long userPhone; 15 | private int state; 16 | private Date createTime; 17 | 18 | //多对一,因为一件商品在库存中有很多数量,对应的购买明细也有很多。 19 | private Seckill seckill; 20 | 21 | 22 | public Seckill getSeckill() { 23 | return seckill; 24 | } 25 | public void setSeckill(Seckill seckill) { 26 | this.seckill = seckill; 27 | } 28 | public long getSeckillId() { 29 | return seckillId; 30 | } 31 | public void setSeckillId(long seckillId) { 32 | this.seckillId = seckillId; 33 | } 34 | public long getUserPhone() { 35 | return userPhone; 36 | } 37 | public void setUserPhone(long userPhone) { 38 | this.userPhone = userPhone; 39 | } 40 | public int getState() { 41 | return state; 42 | } 43 | public void setState(int state) { 44 | this.state = state; 45 | } 46 | public Date getCreateTime() { 47 | return createTime; 48 | } 49 | public void setCreateTime(Date createTime) { 50 | this.createTime = createTime; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state 56 | + ", createTime=" + createTime + "]"; 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/service/SeckillService.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.service; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.stereotype.Service; 6 | 7 | import com.jinsong.dto.Exposer; 8 | import com.jinsong.dto.SeckillExecution; 9 | import com.jinsong.exception.RepeatKillException; 10 | import com.jinsong.exception.SeckillCloseException; 11 | import com.jinsong.exception.SeckillException; 12 | import com.jinsong.model.Seckill; 13 | 14 | @Service 15 | public interface SeckillService { 16 | 17 | /** 18 | * 查询全部的秒杀记录 19 | * @return 20 | */ 21 | List getSeckillList(); 22 | 23 | /** 24 | * 根据商品种类的ID查询商品 25 | * @param seckillId 26 | * @return 27 | */ 28 | Seckill getById(long seckillId); 29 | 30 | /** 31 | * 在秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间 32 | * @param seckillId 33 | */ 34 | Exposer exportSeckillUrl(long seckillId); 35 | 36 | /** 37 | * 执行秒杀操作,有可能失败,有可能成功,所以要抛出我们允许的异常 38 | * @param seckillId 39 | * @param userPhone 40 | * @param md5 41 | * @return 42 | */ 43 | SeckillExecution executeSeckill(long seckillId,long userPhone,String md5) 44 | throws SeckillException,RepeatKillException,SeckillCloseException; 45 | } 46 | -------------------------------------------------------------------------------- /mySecondKill/src/main/java/com/jinsong/service/impl/SeckillServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.service.impl; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springframework.util.DigestUtils; 12 | 13 | import com.jinsong.dao.RedisDAO; 14 | import com.jinsong.dao.SeckillDAO; 15 | import com.jinsong.dao.SuccessKilledDAO; 16 | import com.jinsong.dto.Exposer; 17 | import com.jinsong.dto.SeckillExecution; 18 | import com.jinsong.enums.SeckillStateEnum; 19 | import com.jinsong.exception.RepeatKillException; 20 | import com.jinsong.exception.SeckillCloseException; 21 | import com.jinsong.exception.SeckillException; 22 | import com.jinsong.model.Seckill; 23 | import com.jinsong.model.SuccessKilled; 24 | import com.jinsong.service.SeckillService; 25 | 26 | import ch.qos.logback.core.net.SyslogOutputStream; 27 | 28 | @Service 29 | public class SeckillServiceImpl implements SeckillService { 30 | 31 | // 日志 32 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 33 | 34 | @Autowired 35 | private SeckillDAO seckillDAO; 36 | 37 | @Autowired 38 | private SuccessKilledDAO successKillDAO; 39 | 40 | @Autowired 41 | private RedisDAO redisDAO; 42 | 43 | // 定义MD5所用的盐 44 | private static final String salt = "sdfsdfds2h3iu4y98@&$Yhoihofds"; 45 | 46 | // seckillId进行MD5加密 47 | private String getMD5(long seckillId) { 48 | String base = seckillId + "/" + salt; 49 | String seckillIdMD5 = DigestUtils.md5DigestAsHex(base.getBytes()); 50 | return seckillIdMD5; 51 | } 52 | 53 | /** 54 | * 查询全部的秒杀记录 55 | * 56 | * @return 57 | */ 58 | @Override 59 | public List getSeckillList() { 60 | 61 | // 目前数据库里只有4种商品,所以0到4 62 | return seckillDAO.queryAll(0, 4); 63 | } 64 | 65 | /** 66 | * 根据商品种类的ID查询商品 67 | * 68 | * @param seckillId 69 | * @return 70 | */ 71 | @Override 72 | public Seckill getById(long seckillId) { 73 | return seckillDAO.queryById(seckillId); 74 | } 75 | 76 | /** 77 | * 在秒杀开启时输出秒杀接口的地址 秒杀未开启则输出系统时间和秒杀时间 78 | * 79 | * @param seckillId 80 | */ 81 | @Override 82 | public Exposer exportSeckillUrl(long seckillId) { 83 | 84 | //采用Redis缓存商品种类ID,避免频繁访问Mysql,也就是优化下面这个数据库操作 85 | 86 | //Seckill seckill = seckillDAO.queryById(seckillId); // 获得秒杀的商品种类 87 | 88 | //利用Redis优化数据库操作 89 | //先从Redis缓存中获取seckill对象 90 | Seckill seckill =redisDAO.getSeckill(seckillId); 91 | 92 | 93 | if(seckill==null) { 94 | //如果Redis中没有,则从数据库中获取seckill对象 95 | seckill=seckillDAO.queryById(seckillId); 96 | 97 | if(seckill==null) { 98 | //如果数据库中也没有,则说明没有该商品信息,返回false 99 | return new Exposer(false, seckillId); 100 | }else { 101 | //如果数据库中有该商品信息,将该信息存入Redis,以便用户刷新页面后直接从Redis中获取。 102 | String result =redisDAO.setSeckill(seckill); 103 | 104 | } 105 | }else { 106 | System.out.println("seckil in Redis not null"); 107 | } 108 | 109 | // 当前时间 110 | Date nowTime = new Date(); 111 | 112 | // 商品属性里设定的秒杀开启时间 113 | Date startTime = seckill.getStartTime(); 114 | 115 | // 商品属性里设定的秒杀关闭时间 116 | Date endTime = seckill.getEndTime(); 117 | 118 | if (startTime.getTime() > nowTime.getTime() || endTime.getTime() < nowTime.getTime()) { 119 | // 秒杀还未开始 120 | return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime()); 121 | } 122 | 123 | // 秒杀开启,返回秒杀商品的id、用给接口加密的md5 124 | String md5 = this.getMD5(seckillId); 125 | return new Exposer(true, md5, seckillId); 126 | 127 | } 128 | 129 | /** 130 | * 执行秒杀操作,有可能失败,有可能成功,所以要抛出我们允许的异常 131 | * 132 | * 秒杀是否成功,成功:减库存,增加明细;失败:抛出异常,事务回滚 133 | * 134 | * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格 135 | * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部 136 | * 3.不是所有的方法都需要事务,如只有一条修改操作、只读操作不要事务控制 137 | * 138 | * @param seckillId 139 | * @param userPhone 140 | * @param md5 141 | * @return 142 | */ 143 | @Override 144 | @Transactional // spring boot默认开启了事务,在需要使用事务的地方用注解@Transactional,经过测试,确实有效!!! 145 | public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) 146 | throws SeckillException, RepeatKillException, SeckillCloseException { 147 | if (md5 == null || !md5.equals(getMD5(seckillId))) { 148 | throw new SeckillException("秒杀数据被篡改了,拒绝执行秒杀操作"); 149 | } 150 | 151 | // 执行秒杀逻辑:减库存+增加购买明细 152 | Date nowTime = new Date(); 153 | 154 | // 这段数据库操作的代码可能在任何地方发生未知的异常,所以要整体用try/catch括起来,把所有编译期异常转化为运行期异常 155 | // 运行期异常一旦有错,因为开启了@Transactional,Spring会帮我们回滚 156 | // 之所以要把抛出的RepeatKillException等异常再catch住,是把这些具体的异常类型抛出来, 157 | // 否则省略这段代码的话,RepeatKillException抛出来时就显示为SeckillException,就很笼统了 158 | try { 159 | 160 | // 把用户信息插入秒杀成功表,插入成功代表秒杀成功,返回插入的行数;重复秒杀则返回0;插入失败会返回-1 161 | int insertCount = successKillDAO.insertSuccessKilled(seckillId, userPhone); 162 | if (insertCount <= 0) { 163 | // 重复秒杀 164 | throw new RepeatKillException("重复秒杀,该账号已经秒杀成功过该商品"); 165 | } else { 166 | // successKilled表用户秒杀成功信息插入后,在seckill表中执行减库存操作 167 | // 如果返回值>1,表示更新库存的记录行数,返回值=0,表示更新失败 168 | int updateCount = seckillDAO.reduceNumber(seckillId, nowTime); 169 | 170 | if (updateCount <= 0) { 171 | // 没有更新库存记录,说明秒杀结束 rollback 172 | throw new SeckillCloseException("秒杀已经结束,插入失败"); 173 | } else { 174 | // 秒杀成功,得到成功插入的明细记录,并返回成功秒杀的信息 commit 175 | SuccessKilled successKilled = successKillDAO.queryByIdWithSeckill(seckillId, userPhone); 176 | // SeckillStateEnum.SUCCESS枚举 :SUCCESS(1,"秒杀成功"), 177 | return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled); 178 | } 179 | } 180 | 181 | } catch (RepeatKillException e1) { 182 | 183 | throw e1; 184 | 185 | } catch (SeckillCloseException e2) { 186 | 187 | throw e2; 188 | 189 | } catch (Exception e) { 190 | 191 | logger.error(e.getMessage()); 192 | 193 | // 所有编译期异常转化为运行期异常 194 | throw new SeckillException("运行期内部错误,秒杀失败" + e.getMessage()); 195 | } 196 | 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #数据库配置 2 | spring.datasource.url=jdbc:mysql://localhost:3306/mysecondkill?useUnicode=true&characterEncoding=utf8&useSSL=false 3 | spring.datasource.username=root 4 | spring.datasource.password=83862973 5 | 6 | 7 | 8 | 9 | 10 | 11 | #myabtis全局基本配置 12 | mybatis.config-location=classpath:mybatis-config.xml 13 | 14 | #使用mybatis XML 方式写SQL 就要添加以下两行参数 15 | #type-aliases-package指定Datesource 16 | #使用type-aliases-package,需要配合自动扫描Mappers使用,需要在Mapper接口上标注@Mapper,否则失败 17 | mybatis.type-aliases-package=com.jinsong.model 18 | 19 | #mapper-locations这个配置参数当mapper xml与mapper class不在同一个目录时添加,指定与mapper对应的xml文件在哪里 20 | mybatis.mapper-locations=classpath:mapper/*.xml 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | #thymeleaf配置 29 | #原来关于spring-boot-starter-web等的依赖就可以去掉了,因为spring-boot-starter-thymeleaf是包含这些依赖的 30 | #在spring-boot下,默认约定了Controller试图跳转中thymeleaf模板文件的的前缀prefix是”classpath:/templates/”,后缀suffix是”.html” ,编码是UTF-8,model是HTML5 31 | 32 | #所以下面这句是不用加的 33 | spring.thymeleaf.suffix=.html 34 | 35 | #Spring-boot使用thymeleaf时默认是有缓存的,即你把一个页面代码改了不会刷新页面的效果 36 | #你必须重新运行spring-boot的main()方法才能看到页面更改的效果。 37 | #我们可以把thymeleaf的缓存关掉,添加以下代码 38 | spring.thymeleaf.cache=false 39 | 40 | 41 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/mapper/SeckillDAO.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | UPDATE seckill 11 | SET number = number - 1 12 | WHERE seckill_id=#{seckillId} 13 | AND start_time #{killTime} 14 | AND end_time >= #{killTime} 15 | AND number > 0 16 | 17 | 18 | 23 | 24 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/mapper/SuccessKilledDAO.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | INSERT ignore INTO success_killed(seckill_id,user_phone,state) 12 | VALUES (#{seckillId},#{userPhone},0) 13 | 14 | 15 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/sql/schema.sql: -------------------------------------------------------------------------------- 1 | #数据库初始化脚本 2 | 3 | #创建数据库 4 | DROP database IF EXISTS mySecondKill; 5 | CREATE database mySecondKill DEFAULT CHARSET utf8 COLLATE utf8_general_ci; 6 | 7 | #使用数据库 8 | use mySecondKill; 9 | 10 | #建表seckill,秒杀商品信息表 11 | #ENGINE是引擎,有MyIASM和INNODB两种,MyIASM读取速度更快,且不大量占用内存。INNoDB支持事物 12 | #AUTO_INCREMENT规定自增的起点 13 | #索引加快搜索速度,但降低插入、删除的性能,并且增大内存占用 14 | #注意:字段名要用英文的钝点` 15 | CREATE TABLE seckill( 16 | `seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID', 17 | `name` VARCHAR(120) NOT NULL COMMENT '商品名称', 18 | `number` INT NOT NULL COMMENT '库存数量', 19 | `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间', #没有指定默认值,自动为CURRENT_TIMESTAMP 20 | `end_time` TIMESTAMP NOT NULL DEFAULT '2017-01-01 00:00:00' COMMENT '秒杀结束时间', #mysql5.7以上版本TIMESTAMP默认时间不能为0 21 | `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', #mysql5.7版本以下不能有两个默认值是CURRENT_TIMESTAMP,5.7就可以 22 | PRIMARY KEY (seckill_id), 23 | KEY index_start_time(start_time), 24 | KEY index_end_time(end_time), 25 | KEY index_create_time(create_time) 26 | )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; 27 | 28 | #初始化seckill表数据 29 | INSERT INTO seckill(name,number,start_time,end_time) 30 | VALUES 31 | ('1000元秒杀iPhone7',100,'2017-01-01 00:00:00','2018-12-12 00:00:00'), 32 | ('800元秒杀ipad',200,'2017-01-01 00:00:00','2017-2-12 00:00:00'), 33 | ('5000元秒杀Mac Pro',300,'2018-12-11 00:00:00','2018-12-12 00:00:00'); 34 | 35 | #建表success_killed,秒杀成功明细表 36 | #用户登录认证信息(简化为手机号) 37 | #注意:字段名要用英文的钝点` 38 | CREATE TABLE success_killed( 39 | `seckill_id` BIGINT NOT NULL COMMENT '秒杀商品id', 40 | `user_phone` BIGINT NOT NULL COMMENT '用户手机号', 41 | `state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货', 42 | `create_time` TIMESTAMP NOT NULL COMMENT '订单创建时间', 43 | PRIMARY KEY (seckill_id,user_phone),/*联合主键,确保一个用户只能秒杀一个商品*/ 44 | KEY index_create_time(create_time) 45 | )ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表' -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/static/scripts/seckill.js: -------------------------------------------------------------------------------- 1 | //存放主要交互逻辑的js代码 2 | // javascript 模块化(package.类.方法) 3 | 4 | //逻辑是:首先执行detail:{}里的init初始化函数,在这里面验证是否手机号登录,如果登录了,调用countDown进行倒计时判断 5 | //countDown里判断秒杀是已经结束了还是未开始还是正在进行中 6 | //如果countDown里判断秒杀未开始,则进入倒计时,倒计时结束时调用handlerSeckill暴露秒杀地址,暴露秒杀地址是访问链接/{seckillId}/exposer调用后端控制器函数exposer 7 | //如果countDown里判断秒杀已经开始,则直接调用handlerSeckill暴露秒杀地址 8 | //在handlerSeckill里执行秒杀操作,即访问链接/{seckillId}/{md5}/execution调用后端控制器的秒杀函数execute 9 | 10 | var seckill = { 11 | 12 | //封装秒杀相关ajax的url 13 | URL: { 14 | //注意这里的调用的是访问地址,http://127.0.0.1:8080/time/now 15 | //要与后端控制器里提供的地址一致,我自己控制器里访问地址是不带seckill的 16 | //之前报404错误就是没找到该地址,地址写错了,写成了/seckill/time/now,多了一个seckill 17 | 18 | //部署到服务器上时,这里的路径要写../time/now,不能写成/time/now 19 | //否则会找不到路径,貌似js里的路径和有关js的路径都要这样写!!!! 20 | now: function () { 21 | return '../time/now'; 22 | }, 23 | //暴露秒杀地址 24 | exposer: function (seckillId) { 25 | return '../' + seckillId + '/exposer'; 26 | }, 27 | //执行秒杀 28 | execution: function (seckillId, md5) { 29 | return '../' + seckillId + '/' + md5 + '/execution'; 30 | } 31 | }, 32 | 33 | //验证手机号 34 | validatePhone: function (phone) { 35 | if (phone && phone.length == 11 && !isNaN(phone)) { 36 | return true;//直接判断对象会看对象是否为空,空就是undefine就是false; isNaN 非数字返回true 37 | } else { 38 | return false; 39 | } 40 | }, 41 | 42 | //详情页秒杀逻辑 43 | detail: { 44 | //详情页初始化 45 | init: function (params) { 46 | //手机验证和登录,计时交互 47 | //规划我们的交互流程 48 | //在cookie中查找手机号 49 | var userPhone = $.cookie('userPhone'); 50 | //验证手机号 51 | if (!seckill.validatePhone(userPhone)) { 52 | //绑定手机 控制输出 53 | var killPhoneModal = $('#killPhoneModal'); 54 | killPhoneModal.modal({ 55 | show: true,//显示弹出层 56 | backdrop: false,//禁止位置关闭 57 | keyboard: false//关闭键盘事件 58 | }); 59 | 60 | $('#killPhoneBtn').click(function () { 61 | var inputPhone = $('#killPhoneKey').val(); 62 | console.log("inputPhone: " + inputPhone); 63 | if (seckill.validatePhone(inputPhone)) { 64 | //电话写入cookie(1天过期) 65 | 66 | $.cookie('userPhone', inputPhone, { expires: 1 }); 67 | //验证通过  刷新页面 68 | window.location.reload(); 69 | } else { 70 | //todo 错误文案信息抽取到前端字典里 71 | $('#killPhoneMessage').hide().html('').show(300); 72 | } 73 | }); 74 | } 75 | 76 | //已经登录 77 | //计时交互 78 | var startTime = params['startTime']; 79 | var endTime = params['endTime']; 80 | var seckillId = params['seckillId']; 81 | $.get(seckill.URL.now(), {}, function (result) { 82 | if (result && result['success']) { 83 | var nowTime = result['data']; 84 | //时间判断 计时交互 85 | seckill.countDown(seckillId, nowTime, startTime, endTime); 86 | } else { 87 | console.log('result: ' + result); 88 | alert('result: ' + result); 89 | } 90 | }); 91 | } 92 | }, 93 | 94 | handlerSeckill: function (seckillId, node) { 95 | //获取秒杀地址,控制显示器,执行秒杀 96 | node.hide().html(''); 97 | 98 | $.get(seckill.URL.exposer(seckillId), {}, function (result) { 99 | //在回调函数种执行交互流程 100 | if (result && result['success']) { 101 | var exposer = result['data']; 102 | if (exposer['exposed']) { 103 | //开启秒杀 104 | //获取秒杀地址 105 | var md5 = exposer['md5']; 106 | var killUrl = seckill.URL.execution(seckillId, md5); 107 | console.log("killUrl: " + killUrl); 108 | //绑定一次点击事件 109 | $('#killBtn').one('click', function () { 110 | //执行秒杀请求 111 | //1.先禁用按钮 112 | $(this).addClass('disabled');//,<-$(this)===('#killBtn')-> 113 | //2.发送秒杀请求执行秒杀 114 | $.post(killUrl, {}, function (result) { 115 | if (result && result['success']) { 116 | var killResult = result['data']; 117 | var state = killResult['state']; 118 | var stateInfo = killResult['stateInfo']; 119 | //显示秒杀结果 120 | node.html('' + stateInfo + ''); 121 | } 122 | }); 123 | }); 124 | node.show(); 125 | } else { 126 | //未开启秒杀(浏览器计时偏差) 127 | var now = exposer['now']; 128 | var start = exposer['start']; 129 | var end = exposer['end']; 130 | seckill.countDown(seckillId, now, start, end); 131 | } 132 | } else { 133 | console.log('result: ' + result); 134 | } 135 | }); 136 | 137 | }, 138 | 139 | countDown: function (seckillId, nowTime, startTime, endTime) { 140 | console.log(seckillId + '_' + nowTime + '_' + startTime + '_' + endTime); 141 | var seckillBox = $('#seckill-box'); 142 | if (nowTime > endTime) { 143 | //秒杀结束 144 | seckillBox.html('秒杀结束!'); 145 | } else if (nowTime < startTime) { 146 | //秒杀未开始,计时事件绑定 147 | var killTime = new Date(startTime + 1000);//todo 防止时间偏移 148 | seckillBox.countdown(killTime, function (event) { 149 | //时间格式 150 | var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒 '); 151 | seckillBox.html(format); 152 | }).on('finish.countdown', function () { 153 | //时间完成后回调事件 154 | //获取秒杀地址,控制现实逻辑,执行秒杀 155 | console.log('______fininsh.countdown'); 156 | seckill.handlerSeckill(seckillId, seckillBox); 157 | }); 158 | } else { 159 | //秒杀开始 160 | seckill.handlerSeckill(seckillId, seckillBox); 161 | } 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/templates/detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 秒杀商品列表 5 | 6 | 7 | 8 | 11 | 12 | 13 |
14 |
15 |
16 |

商品名称

17 |
18 | 19 |
20 |

21 | 22 | 23 |

24 |
25 |
26 |
27 | 28 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 93 | -------------------------------------------------------------------------------- /mySecondKill/src/main/resources/templates/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 秒杀商品列表 5 | 6 | 7 | 8 | 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 | 36 | 37 | 38 | 40 | 41 | 42 | 43 |
名称库存开始时间结束时间创建时间详情页
名称库存秒杀开启时间秒杀结束时间秒杀创建时间详情页
44 | 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /mySecondKill/src/test/java/com/jinsong/MySecondKillApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.jinsong; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class MySecondKillApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mySecondKill/src/test/java/com/jinsong/dao/SeckillDAOTest.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dao; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import com.jinsong.model.Seckill; 17 | 18 | /** 19 | * SpringBoot中进行单元测试 20 | * 需要添加以下两行注释 21 | * @RunWith(SpringRunner.class) :告诉Junit运行使用Spring 的单元测试支持,SpringRunner是SpringJunit4ClassRunner新的名称,只是视觉上看起来更简单了 22 | * @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) :该注解可以在一个测试类指定运行Spring Boot为基础的测试 23 | * 24 | * 25 | */ 26 | @RunWith(SpringRunner.class) 27 | @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 28 | public class SeckillDAOTest { 29 | 30 | @Autowired 31 | private SeckillDAO seckillDAO; 32 | 33 | @Test 34 | public void testReduceNumber() { 35 | 36 | long seckillId=1000; 37 | Date date=new Date(); 38 | int updateCount=seckillDAO.reduceNumber(seckillId,date); 39 | System.out.println(updateCount); 40 | } 41 | 42 | @Test 43 | public void testQueryById() { 44 | long seckillId=1000; 45 | Seckill seckill=seckillDAO.queryById(seckillId); 46 | System.out.println(seckill); 47 | 48 | } 49 | 50 | @Test 51 | public void testQueryAll() { 52 | List seckills=seckillDAO.queryAll(0,100); 53 | for (Seckill seckill : seckills) 54 | { 55 | System.out.println(seckill); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /mySecondKill/src/test/java/com/jinsong/dao/SuccessKilledDAOTest.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.dao; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import com.jinsong.model.SuccessKilled; 13 | 14 | /** 15 | * SpringBoot中进行单元测试 16 | * 需要添加以下两行注释 17 | * @RunWith(SpringRunner.class) :告诉Junit运行使用Spring 的单元测试支持,SpringRunner是SpringJunit4ClassRunner新的名称,只是视觉上看起来更简单了 18 | * @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) :该注解可以在一个测试类指定运行Spring Boot为基础的测试 19 | * 20 | * 21 | */ 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 24 | public class SuccessKilledDAOTest { 25 | 26 | @Autowired 27 | private SuccessKilledDAO successKilledDAO; 28 | 29 | @Test 30 | public void insertSuccessKilled() throws Exception { 31 | 32 | long seckillId=1000L; 33 | long userPhone=13476191877L; 34 | int insertCount=successKilledDAO.insertSuccessKilled(seckillId,userPhone); 35 | System.out.println("insertCount="+insertCount); 36 | } 37 | 38 | @Test 39 | public void queryByIdWithSeckill() throws Exception { 40 | long seckillId=1000L; 41 | long userPhone=13476191877L; 42 | SuccessKilled successKilled=successKilledDAO.queryByIdWithSeckill(seckillId,userPhone); 43 | System.out.println(successKilled); 44 | System.out.println(successKilled.getSeckill()); 45 | 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /mySecondKill/src/test/java/com/jinsong/service/impl/SeckillServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jinsong.service.impl; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import com.jinsong.dto.Exposer; 17 | import com.jinsong.dto.SeckillExecution; 18 | import com.jinsong.exception.RepeatKillException; 19 | import com.jinsong.exception.SeckillCloseException; 20 | import com.jinsong.model.Seckill; 21 | import com.jinsong.service.SeckillService; 22 | 23 | /** 24 | * SpringBoot中进行单元测试 需要添加以下两行注释 25 | * 26 | * @RunWith(SpringRunner.class) :告诉Junit运行使用Spring 27 | * 的单元测试支持,SpringRunner是SpringJunit4ClassRunner新的名称,只是视觉上看起来更简单了 28 | * @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) :该注解可以在一个测试类指定运行Spring 29 | * Boot为基础的测试 30 | * 31 | * 32 | */ 33 | @RunWith(SpringRunner.class) 34 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 35 | public class SeckillServiceImplTest { 36 | 37 | // 日志 38 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 39 | 40 | @Autowired 41 | private SeckillService seckillService; 42 | 43 | @Test 44 | public void getSeckillList() throws Exception { 45 | List seckills = seckillService.getSeckillList(); 46 | System.out.println(seckills); 47 | 48 | } 49 | 50 | @Test 51 | public void getById() throws Exception { 52 | 53 | long seckillId = 1000; 54 | Seckill seckill = seckillService.getById(seckillId); 55 | System.out.println(seckill); 56 | } 57 | 58 | @Test // 完整逻辑代码测试,注意可重复执行 59 | public void testSeckillLogic() throws Exception { 60 | long seckillId = 1000; 61 | Exposer exposer = seckillService.exportSeckillUrl(seckillId); 62 | if (exposer.isExposed()) { 63 | 64 | System.out.println(exposer); 65 | 66 | long userPhone = 134761909L; 67 | String md5 = exposer.getMd5(); 68 | 69 | try { 70 | SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, userPhone, md5); 71 | System.out.println(seckillExecution); 72 | } catch (RepeatKillException e) { 73 | e.printStackTrace(); 74 | } catch (SeckillCloseException e1) { 75 | e1.printStackTrace(); 76 | } 77 | } else { 78 | // 秒杀未开启 79 | System.out.println(exposer); 80 | } 81 | } 82 | 83 | @Test 84 | public void executeSeckill() throws Exception { 85 | 86 | long seckillId = 1000; 87 | String md5 = "bf204e2683e7452aa7db1a50b5713bae"; 88 | 89 | } 90 | 91 | } 92 | --------------------------------------------------------------------------------