└── 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 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
47 |
48 |
55 |
56 |
57 |
58 |
59 |
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 |
--------------------------------------------------------------------------------