mirror of
https://gitee.com/52itstyle/spring-boot-seckill.git
synced 2025-12-30 02:12:27 +00:00
秒杀初交
This commit is contained in:
32
.classpath
Normal file
32
.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="java"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
42
.project
Normal file
42
.project
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>spring-boot-seckill</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
.settings/.jsdtscope
Normal file
13
.settings/.jsdtscope
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src/main/webapp"/>
|
||||
<classpathentry kind="src" path="target/m2e-wtp/web-resources"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
|
||||
<attributes>
|
||||
<attribute name="hide" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
|
||||
<classpathentry kind="output" path=""/>
|
||||
</classpath>
|
||||
13
.settings/org.eclipse.jdt.core.prefs
Normal file
13
.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,13 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
11
.settings/org.eclipse.wst.common.component
Normal file
11
.settings/org.eclipse.wst.common.component
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
<wb-module deploy-name="spring-boot-seckill">
|
||||
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
|
||||
<property name="context-root" value="spring-boot-seckill"/>
|
||||
<property name="java-output-path" value="/spring-boot-seckill/target/classes"/>
|
||||
</wb-module>
|
||||
</project-modules>
|
||||
7
.settings/org.eclipse.wst.common.project.facet.core.xml
Normal file
7
.settings/org.eclipse.wst.common.project.facet.core.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<faceted-project>
|
||||
<fixed facet="wst.jsdt.web"/>
|
||||
<installed facet="jst.web" version="2.3"/>
|
||||
<installed facet="wst.jsdt.web" version="1.0"/>
|
||||
<installed facet="java" version="1.7"/>
|
||||
</faceted-project>
|
||||
1
.settings/org.eclipse.wst.jsdt.ui.superType.container
Normal file
1
.settings/org.eclipse.wst.jsdt.ui.superType.container
Normal file
@@ -0,0 +1 @@
|
||||
org.eclipse.wst.jsdt.launching.baseBrowserLibrary
|
||||
1
.settings/org.eclipse.wst.jsdt.ui.superType.name
Normal file
1
.settings/org.eclipse.wst.jsdt.ui.superType.name
Normal file
@@ -0,0 +1 @@
|
||||
Window
|
||||
2
.settings/org.eclipse.wst.validation.prefs
Normal file
2
.settings/org.eclipse.wst.validation.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
disabled=06target
|
||||
eclipse.preferences.version=1
|
||||
106
pom.xml
Normal file
106
pom.xml
Normal file
@@ -0,0 +1,106 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.itstyle.seckill</groupId>
|
||||
<artifactId>spring-boot-seckill</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-seckill Maven</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<docker.image.prefix>springboot</docker.image.prefix>
|
||||
</properties>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.10.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<!-- 实现web功能 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- 模版 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.nekohtml</groupId>
|
||||
<artifactId>nekohtml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<!-- spring-boot-starter-redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-redis</artifactId>
|
||||
<version>1.4.7.RELEASE</version>
|
||||
</dependency>
|
||||
<!-- 构建Restful API -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
<!-- Redisson实现分布式锁 -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>2.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.7</version>
|
||||
</dependency>
|
||||
<!--kafka支持-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
<version>1.3.5.RELEASE</version><!--$NO-MVN-MAN-VER$-->
|
||||
</dependency>
|
||||
<!-- zookeeper 分布式锁、注意zookeeper版本 这里对应的是3.4.6-->
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<version>2.10.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>spring-boot-seckill</finalName><plugins>
|
||||
<!-- 打包项目 mvn clean package -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<dependencies>
|
||||
<!-- mvn spring-boot:run 热部署启动 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>springloaded</artifactId>
|
||||
<version>1.2.7.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
24
src/main/java/com/itstyle/seckill/Application.java
Normal file
24
src/main/java/com/itstyle/seckill/Application.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.itstyle.seckill;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
/**
|
||||
* 启动类
|
||||
* 创建者 科帮网
|
||||
* 创建时间 2018年5月12日
|
||||
* API接口测试:http://localhost:8080/seckill/swagger-ui.html
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(Application.class);
|
||||
/**
|
||||
* 1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁
|
||||
* 4. redis 订阅监听;5.kafka消息队列
|
||||
*/
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
SpringApplication.run(Application.class, args);
|
||||
LOGGER.info("项目启动 ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.itstyle.seckill.common.api;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
@Bean
|
||||
public Docket userApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2).groupName("秒杀案例").apiInfo(apiInfo()).select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.itstyle.seckill.web")).paths(PathSelectors.any()).build();
|
||||
}
|
||||
// 预览地址:swagger-ui.html
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder().title("Spring 中使用Swagger2构建文档").termsOfServiceUrl("https://blog.52itstyle.com")
|
||||
.contact(new Contact("科帮网 ", "https://blog.52itstyle.com/", "345849402@qq.com")).version("1.1").build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.itstyle.seckill.common.config;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
/**
|
||||
* 通用访问拦截匹配
|
||||
* 创建者 科帮网
|
||||
* 创建时间 2018年4月3日
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
/**
|
||||
* 页面跳转
|
||||
* @param module
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("{url}.shtml")
|
||||
public String page(@PathVariable("url") String url) {
|
||||
return url;
|
||||
}
|
||||
/**
|
||||
* 页面跳转(二级目录)
|
||||
* @param module
|
||||
* @param function
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("{module}/{url}.shtml")
|
||||
public String page(@PathVariable("module") String module,@PathVariable("url") String url) {
|
||||
return module + "/" + url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.itstyle.seckill.common.dynamicquery;
|
||||
import java.util.List;
|
||||
/**
|
||||
* 扩展SpringDataJpa, 支持动态jpql/nativesql查询并支持分页查询
|
||||
* 使用方法:注入ServiceImpl
|
||||
* 创建者 张志朋
|
||||
* 创建时间 2018年3月8日
|
||||
*/
|
||||
public interface DynamicQuery {
|
||||
|
||||
public void save(Object entity);
|
||||
|
||||
public void update(Object entity);
|
||||
|
||||
public <T> void delete(Class<T> entityClass, Object entityid);
|
||||
|
||||
public <T> void delete(Class<T> entityClass, Object[] entityids);
|
||||
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List
|
||||
* @param resultClass
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryList(String nativeSql, Object... params);
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List<Map<key,value>>
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryListMap(String nativeSql,Object... params);
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List<组合对象>
|
||||
* @param resultClass
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryListModel(Class<T> resultClass, String nativeSql, Object... params);
|
||||
|
||||
/**
|
||||
* 执行nativeSql统计查询
|
||||
* @param nativeSql
|
||||
* @param params 占位符参数(例如?1)绑定的参数值
|
||||
* @return 统计条数
|
||||
*/
|
||||
Object nativeQueryObject(String nativeSql, Object... params);
|
||||
/**
|
||||
* 执行nativeSql统计查询
|
||||
* @param nativeSql
|
||||
* @param params 占位符参数(例如?1)绑定的参数值
|
||||
* @return 统计条数
|
||||
*/
|
||||
Object[] nativeQueryArray(String nativeSql, Object... params);
|
||||
|
||||
/**
|
||||
* 执行nativeSql的update,delete操作
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
int nativeExecuteUpdate(String nativeSql, Object... params);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.itstyle.seckill.common.dynamicquery;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.hibernate.SQLQuery;
|
||||
import org.hibernate.transform.Transformers;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Repository;
|
||||
/**
|
||||
* 动态jpql/nativesql查询的实现类
|
||||
* 创建者 张志朋
|
||||
* 创建时间 2018年3月8日
|
||||
*/
|
||||
@Repository
|
||||
public class DynamicQueryImpl implements DynamicQuery {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(DynamicQueryImpl.class);
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
public EntityManager getEntityManager() {
|
||||
return em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Object entity) {
|
||||
em.persist(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
em.merge(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void delete(Class<T> entityClass, Object entityid) {
|
||||
delete(entityClass, new Object[] { entityid });
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void delete(Class<T> entityClass, Object[] entityids) {
|
||||
for (Object id : entityids) {
|
||||
em.remove(em.getReference(entityClass, id));
|
||||
}
|
||||
}
|
||||
private Query createNativeQuery(String sql, Object... params) {
|
||||
Query q = em.createNativeQuery(sql);
|
||||
if (params != null && params.length > 0) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
q.setParameter(i + 1, params[i]); // 与Hiberante不同,jpa
|
||||
// query从位置1开始
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> nativeQueryList(String nativeSql, Object... params) {
|
||||
Query q = createNativeQuery(nativeSql, params);
|
||||
q.unwrap(SQLQuery.class).setResultTransformer(Transformers.TO_LIST);
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> nativeQueryListModel(Class<T> resultClass,
|
||||
String nativeSql, Object... params) {
|
||||
Query q = createNativeQuery(nativeSql, params);;
|
||||
q.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass));
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> nativeQueryListMap(String nativeSql, Object... params) {
|
||||
Query q = createNativeQuery(nativeSql, params);
|
||||
q.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nativeQueryObject(String nativeSql, Object... params) {
|
||||
return createNativeQuery(nativeSql, params).getSingleResult();
|
||||
}
|
||||
@Override
|
||||
public int nativeExecuteUpdate(String nativeSql, Object... params) {
|
||||
return createNativeQuery(nativeSql, params).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] nativeQueryArray(String nativeSql, Object... params) {
|
||||
return (Object[]) createNativeQuery(nativeSql, params).getSingleResult();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.itstyle.seckill.common.dynamicquery;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeQueryResultEntity {
|
||||
|
||||
}
|
||||
59
src/main/java/com/itstyle/seckill/common/entity/Result.java
Normal file
59
src/main/java/com/itstyle/seckill/common/entity/Result.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.itstyle.seckill.common.entity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
/**
|
||||
* 页面响应entity
|
||||
* 创建者 张志朋
|
||||
* 创建时间 2018年3月8日
|
||||
*/
|
||||
public class Result extends HashMap<String, Object> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public Result() {
|
||||
put("code", 0);
|
||||
}
|
||||
|
||||
public static Result error() {
|
||||
return error(500, "未知异常,请联系管理员");
|
||||
}
|
||||
|
||||
public static Result error(String msg) {
|
||||
return error(500, msg);
|
||||
}
|
||||
|
||||
public static Result error(int code, String msg) {
|
||||
Result r = new Result();
|
||||
r.put("code", code);
|
||||
r.put("msg", msg);
|
||||
return r;
|
||||
}
|
||||
public static Result error(Object msg) {
|
||||
Result r = new Result();
|
||||
r.put("msg", msg);
|
||||
return r;
|
||||
}
|
||||
public static Result ok(Object msg) {
|
||||
Result r = new Result();
|
||||
r.put("msg", msg);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
public static Result ok(Map<String, Object> map) {
|
||||
Result r = new Result();
|
||||
r.putAll(map);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Result ok() {
|
||||
return new Result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result put(String key, Object value) {
|
||||
super.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/itstyle/seckill/common/entity/Seckill.java
Normal file
81
src/main/java/com/itstyle/seckill/common/entity/Seckill.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.itstyle.seckill.common.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
@Entity
|
||||
@Table(name = "seckill")
|
||||
public class Seckill implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(name = "seckill_id", nullable = false)
|
||||
private long seckillId;
|
||||
private String name;
|
||||
private int number;
|
||||
private Timestamp startTime;
|
||||
private Timestamp endTime;
|
||||
private Timestamp createTime;
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getSeckillId() {
|
||||
return seckillId;
|
||||
}
|
||||
public void setSeckillId(long seckillId) {
|
||||
this.seckillId = seckillId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public Timestamp getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(Timestamp startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public Timestamp getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(Timestamp endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public Timestamp getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Timestamp createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.itstyle.seckill.common.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
@Entity
|
||||
@Table(name = "success_killed")
|
||||
public class SuccessKilled implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Id
|
||||
@Column(name = "seckill_id", nullable = false)
|
||||
private long seckillId;
|
||||
@Id
|
||||
private long userId;
|
||||
private short state;
|
||||
private Timestamp createTime;
|
||||
|
||||
public long getSeckillId() {
|
||||
return seckillId;
|
||||
}
|
||||
|
||||
public void setSeckillId(long seckillId) {
|
||||
this.seckillId = seckillId;
|
||||
}
|
||||
|
||||
public long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public short getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(short state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public Timestamp getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Timestamp createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.itstyle.seckill.common.enums;
|
||||
|
||||
public enum SeckillStatEnum {
|
||||
|
||||
SUCCESS(1,"秒杀成功"),
|
||||
END(0,"秒杀结束"),
|
||||
REPEAT_KILL(-1,"重复秒杀"),
|
||||
INNER_ERROR(-2,"系统异常"),
|
||||
DATE_REWRITE(-3,"数据篡改");
|
||||
|
||||
private int state;
|
||||
private String info;
|
||||
|
||||
SeckillStatEnum(int state, String info) {
|
||||
this.state = state;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static SeckillStatEnum stateOf(int index)
|
||||
{
|
||||
for (SeckillStatEnum state : values())
|
||||
{
|
||||
if (state.getState()==index)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.itstyle.seckill.common.interceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
/**
|
||||
* 配置首页
|
||||
* 创建者 小柒2012
|
||||
* 创建时间 2017年9月7日
|
||||
*/
|
||||
@Configuration
|
||||
public class MyAdapter extends WebMvcConfigurerAdapter{
|
||||
@Override
|
||||
public void addViewControllers( ViewControllerRegistry registry ) {
|
||||
registry.addViewController( "/" ).setViewName( "forward:/login.shtml" );
|
||||
registry.setOrder( Ordered.HIGHEST_PRECEDENCE );
|
||||
super.addViewControllers( registry );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.itstyle.seckill.common.redis;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig extends CachingConfigurerSupport {
|
||||
/**
|
||||
* 自定义key(消息队列 暂时用不到 自行忽略)
|
||||
* 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。
|
||||
* @Author 科帮网
|
||||
* @return
|
||||
* @Date 2017年8月13日
|
||||
* 更新日志
|
||||
* 2017年8月13日 科帮网 首次创建
|
||||
*
|
||||
*/
|
||||
@Bean
|
||||
public KeyGenerator keyGenerator() {
|
||||
return new KeyGenerator() {
|
||||
@Override
|
||||
public Object generate(Object target, Method method,
|
||||
Object... params) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(target.getClass().getName());
|
||||
sb.append(method.getName());
|
||||
for (Object obj : params) {
|
||||
sb.append(obj.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 缓存管理器
|
||||
* @Author 科帮网
|
||||
* @param redisTemplate
|
||||
* @return CacheManager
|
||||
* @Date 2017年8月13日
|
||||
* 更新日志
|
||||
* 2017年8月13日 科帮网 首次创建
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisTemplate redisTemplate) {
|
||||
return new RedisCacheManager(redisTemplate);
|
||||
}
|
||||
/**
|
||||
* 序列化Java对象
|
||||
* @Author 科帮网
|
||||
* @param factory
|
||||
* @return RedisTemplate<Object, Object>
|
||||
* @Date 2017年8月13日
|
||||
* 更新日志
|
||||
* 2017年8月13日 科帮网 首次创建
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
|
||||
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(mapper);
|
||||
template.setValueSerializer(serializer);
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
130
src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java
Normal file
130
src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package com.itstyle.seckill.common.redis;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Component;
|
||||
/**
|
||||
* 缓存工具类
|
||||
* 创建者 科帮网
|
||||
* 创建时间 2018年4月8日
|
||||
*/
|
||||
@Component
|
||||
public class RedisUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<Serializable, Serializable> redisTemplate;
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
public static final String KEY_PREFIX_VALUE = "itstyle:seckill:value:";
|
||||
|
||||
|
||||
/**
|
||||
* 缓存value操作
|
||||
* @param k
|
||||
* @param v
|
||||
* @param time
|
||||
* @return
|
||||
*/
|
||||
public boolean cacheValue(String k, Serializable v, long time) {
|
||||
String key = KEY_PREFIX_VALUE + k;
|
||||
try {
|
||||
ValueOperations<Serializable, Serializable> valueOps = redisTemplate.opsForValue();
|
||||
valueOps.set(key, v);
|
||||
if (time > 0) redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
logger.error("缓存[{}]失败, value[{}]",key,v,t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 缓存value操作
|
||||
* @Author 科帮网
|
||||
* @param k
|
||||
* @param v
|
||||
* @param time
|
||||
* @param unit
|
||||
* @return boolean
|
||||
* @Date 2017年12月23日
|
||||
* 更新日志
|
||||
* 2017年12月23日 科帮网 首次创建
|
||||
*
|
||||
*/
|
||||
public boolean cacheValue(String k, Serializable v, long time,TimeUnit unit) {
|
||||
String key = KEY_PREFIX_VALUE + k;
|
||||
try {
|
||||
ValueOperations<Serializable, Serializable> valueOps = redisTemplate.opsForValue();
|
||||
valueOps.set(key, v);
|
||||
if (time > 0) redisTemplate.expire(key, time, unit);
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
logger.error("缓存[{}]失败, value[{}]",key,v,t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存value操作
|
||||
* @param k
|
||||
* @param v
|
||||
* @return
|
||||
*/
|
||||
public boolean cacheValue(String k, Serializable v) {
|
||||
return cacheValue(k, v, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断缓存是否存在
|
||||
* @param k
|
||||
* @return
|
||||
*/
|
||||
public boolean containsValueKey(String k) {
|
||||
String key = KEY_PREFIX_VALUE + k;
|
||||
try {
|
||||
return redisTemplate.hasKey(key);
|
||||
} catch (Throwable t) {
|
||||
logger.error("判断缓存存在失败key[" + key + ", error[" + t + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 获取缓存
|
||||
* @param k
|
||||
* @return
|
||||
*/
|
||||
public Serializable getValue(String k) {
|
||||
try {
|
||||
ValueOperations<Serializable, Serializable> valueOps = redisTemplate.opsForValue();
|
||||
return valueOps.get(KEY_PREFIX_VALUE + k);
|
||||
} catch (Throwable t) {
|
||||
logger.error("获取缓存失败key[" + KEY_PREFIX_VALUE + k + ", error[" + t + "]");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 移除缓存
|
||||
* @param k
|
||||
* @return
|
||||
*/
|
||||
public boolean removeValue(String k) {
|
||||
String key = KEY_PREFIX_VALUE + k;
|
||||
try {
|
||||
redisTemplate.delete(key);
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
logger.error("获取缓存失败key[" + key + ", error[" + t + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.itstyle.seckill.distributedlock.redis;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonMultiLock;
|
||||
import org.redisson.RedissonRedLock;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* redis分布式锁Demo
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
public class RedissLockDemo {
|
||||
/**
|
||||
* 可重入锁(Reentrant Lock)
|
||||
* Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁
|
||||
* @param redisson
|
||||
*/
|
||||
public void testReentrantLock(RedissonClient redisson) {
|
||||
RLock lock = redisson.getLock("anyLock");
|
||||
try {
|
||||
// 1. 最常见的使用方法
|
||||
// lock.lock();
|
||||
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
|
||||
// lock.lock(10, TimeUnit.SECONDS);
|
||||
// 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁
|
||||
boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
|
||||
if (res) { // 成功
|
||||
// do your business
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Redisson同时还为分布式锁提供了异步执行的相关方法
|
||||
* @param redisson
|
||||
*/
|
||||
public void testAsyncReentrantLock(RedissonClient redisson) {
|
||||
RLock lock = redisson.getLock("anyLock");
|
||||
try {
|
||||
lock.lockAsync();
|
||||
lock.lockAsync(10, TimeUnit.SECONDS);
|
||||
Future<Boolean> res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS);
|
||||
if (res.get()) {
|
||||
// do your business
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 公平锁(Fair Lock)
|
||||
* Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。
|
||||
* 在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
|
||||
* @param redisson
|
||||
*/
|
||||
public void testFairLock(RedissonClient redisson){
|
||||
RLock fairLock = redisson.getFairLock("anyLock");
|
||||
try{
|
||||
// 最常见的使用方法
|
||||
fairLock.lock();
|
||||
// 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁
|
||||
fairLock.lock(10, TimeUnit.SECONDS);
|
||||
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
|
||||
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
|
||||
if (res) {
|
||||
// do your business
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
fairLock.unlock();
|
||||
}
|
||||
// Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:
|
||||
// RLock fairLock = redisson.getFairLock("anyLock");
|
||||
// fairLock.lockAsync();
|
||||
// fairLock.lockAsync(10, TimeUnit.SECONDS);
|
||||
// Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
/**
|
||||
* 联锁(MultiLock)
|
||||
* Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例
|
||||
* @param redisson1
|
||||
* @param redisson2
|
||||
* @param redisson3
|
||||
*/
|
||||
public void testMultiLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){
|
||||
RLock lock1 = redisson1.getLock("lock1");
|
||||
RLock lock2 = redisson2.getLock("lock2");
|
||||
RLock lock3 = redisson3.getLock("lock3");
|
||||
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
|
||||
try {
|
||||
// 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。
|
||||
lock.lock();
|
||||
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
|
||||
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
|
||||
if (res) {
|
||||
// do your business
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 红锁(RedLock)
|
||||
* Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例
|
||||
* @param redisson1
|
||||
* @param redisson2
|
||||
* @param redisson3
|
||||
*/
|
||||
public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){
|
||||
RLock lock1 = redisson1.getLock("lock1");
|
||||
RLock lock2 = redisson2.getLock("lock2");
|
||||
RLock lock3 = redisson3.getLock("lock3");
|
||||
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
|
||||
try {
|
||||
// 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。
|
||||
lock.lock();
|
||||
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
|
||||
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
|
||||
if (res) {
|
||||
// do your business
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
//读写锁(ReadWriteLock)、信号量(Semaphore)、可过期性信号量(PermitExpirableSemaphore)、闭锁(CountDownLatch)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.itstyle.seckill.distributedlock.redis;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* redis分布式锁帮助类
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
public class RedissLockUtil {
|
||||
private static RedissonClient redissonClient;
|
||||
|
||||
public void setRedissonClient(RedissonClient locker) {
|
||||
redissonClient = locker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加锁
|
||||
* @param lockKey
|
||||
* @return
|
||||
*/
|
||||
public static RLock lock(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock();
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
* @param lockKey
|
||||
*/
|
||||
public static void unlock(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
* @param lock
|
||||
*/
|
||||
public static void unlock(RLock lock) {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* 带超时的锁
|
||||
* @param lockKey
|
||||
* @param timeout 超时时间 单位:秒
|
||||
*/
|
||||
public static RLock lock(String lockKey, int timeout) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock(timeout, TimeUnit.SECONDS);
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带超时的锁
|
||||
* @param lockKey
|
||||
* @param unit 时间单位
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public static RLock lock(String lockKey, TimeUnit unit ,int timeout) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock(timeout, unit);
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
* @param lockKey
|
||||
* @param waitTime 最多等待时间
|
||||
* @param leaseTime 上锁后自动释放锁时间
|
||||
* @return
|
||||
*/
|
||||
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
try {
|
||||
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
* @param lockKey
|
||||
* @param unit 时间单位
|
||||
* @param waitTime 最多等待时间
|
||||
* @param leaseTime 上锁后自动释放锁时间
|
||||
* @return
|
||||
*/
|
||||
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
try {
|
||||
return lock.tryLock(waitTime, leaseTime, unit);
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.itstyle.seckill.distributedlock.redis;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
//import org.redisson.config.SentinelServersConfig;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(Config.class)
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedissonAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private RedissonProperties redssionProperties;
|
||||
|
||||
/**
|
||||
* 哨兵模式自动装配
|
||||
* @return
|
||||
*/
|
||||
// @Bean
|
||||
// @ConditionalOnProperty(name="redisson.master-name")
|
||||
// RedissonClient redissonSentinel() {
|
||||
// Config config = new Config();
|
||||
// SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
|
||||
// .setMasterName(redssionProperties.getMasterName())
|
||||
// .setTimeout(redssionProperties.getTimeout())
|
||||
// .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
|
||||
// .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
|
||||
//
|
||||
// if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
|
||||
// serverConfig.setPassword(redssionProperties.getPassword());
|
||||
// }
|
||||
// return Redisson.create(config);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 单机模式自动装配
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(name="redisson.address")
|
||||
RedissonClient redissonSingle() {
|
||||
Config config = new Config();
|
||||
SingleServerConfig serverConfig = config.useSingleServer()
|
||||
.setAddress(redssionProperties.getAddress())
|
||||
.setTimeout(redssionProperties.getTimeout())
|
||||
.setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
|
||||
if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
|
||||
serverConfig.setPassword(redssionProperties.getPassword());
|
||||
}
|
||||
|
||||
return Redisson.create(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配locker类,并将实例注入到RedissLockUtil中
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
RedissLockUtil redissLockUtil(RedissonClient redissonClient) {
|
||||
RedissLockUtil redissLockUtil = new RedissLockUtil();
|
||||
redissLockUtil.setRedissonClient(redissonClient);
|
||||
return redissLockUtil;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.itstyle.seckill.distributedlock.redis;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
private int timeout = 3000;
|
||||
|
||||
private String address;
|
||||
|
||||
private String password;
|
||||
|
||||
private int connectionPoolSize = 64;
|
||||
|
||||
private int connectionMinimumIdleSize=10;
|
||||
|
||||
private int slaveConnectionPoolSize = 250;
|
||||
|
||||
private int masterConnectionPoolSize = 250;
|
||||
|
||||
private String[] sentinelAddresses;
|
||||
|
||||
private String masterName;
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public int getSlaveConnectionPoolSize() {
|
||||
return slaveConnectionPoolSize;
|
||||
}
|
||||
|
||||
public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) {
|
||||
this.slaveConnectionPoolSize = slaveConnectionPoolSize;
|
||||
}
|
||||
|
||||
public int getMasterConnectionPoolSize() {
|
||||
return masterConnectionPoolSize;
|
||||
}
|
||||
|
||||
public void setMasterConnectionPoolSize(int masterConnectionPoolSize) {
|
||||
this.masterConnectionPoolSize = masterConnectionPoolSize;
|
||||
}
|
||||
|
||||
public String[] getSentinelAddresses() {
|
||||
return sentinelAddresses;
|
||||
}
|
||||
|
||||
public void setSentinelAddresses(String sentinelAddresses) {
|
||||
this.sentinelAddresses = sentinelAddresses.split(",");
|
||||
}
|
||||
|
||||
public String getMasterName() {
|
||||
return masterName;
|
||||
}
|
||||
|
||||
public void setMasterName(String masterName) {
|
||||
this.masterName = masterName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize() {
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize) {
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionMinimumIdleSize() {
|
||||
return connectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
|
||||
this.connectionMinimumIdleSize = connectionMinimumIdleSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.itstyle.seckill.distributedlock.zookeeper;
|
||||
|
||||
import org.apache.curator.RetryPolicy;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
/**
|
||||
* zookeeper 分布式锁
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
public class ZkLockUtil{
|
||||
|
||||
@Value("${zookeeper.address}")
|
||||
private static String address;
|
||||
|
||||
public static CuratorFramework client;
|
||||
|
||||
static{
|
||||
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
|
||||
client = CuratorFrameworkFactory.newClient(address, retryPolicy);
|
||||
client.start();
|
||||
}
|
||||
/**
|
||||
* 私有的默认构造子,保证外界无法直接实例化
|
||||
*/
|
||||
private ZkLockUtil(){};
|
||||
/**
|
||||
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
|
||||
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
|
||||
*/
|
||||
private static class SingletonHolder{
|
||||
/**
|
||||
* 静态初始化器,由JVM来保证线程安全
|
||||
*/
|
||||
private static InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
|
||||
}
|
||||
public static InterProcessMutex getMutex(){
|
||||
return SingletonHolder.mutex;
|
||||
}
|
||||
//获得了锁
|
||||
public static void acquire(){
|
||||
try {
|
||||
getMutex().acquire();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
//释放锁
|
||||
public static void release(){
|
||||
try {
|
||||
getMutex().release();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.itstyle.seckill.queue.jvm;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.itstyle.seckill.common.entity.SuccessKilled;
|
||||
/**
|
||||
* 秒杀队列(固定长度为100)
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
* 创建时间 2018年5月10日
|
||||
*/
|
||||
public class SeckillQueue {
|
||||
//队列大小
|
||||
static final int QUEUE_MAX_SIZE = 100;
|
||||
|
||||
static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);
|
||||
|
||||
/**
|
||||
* 私有的默认构造子,保证外界无法直接实例化
|
||||
*/
|
||||
private SeckillQueue(){};
|
||||
/**
|
||||
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
|
||||
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
|
||||
*/
|
||||
private static class SingletonHolder{
|
||||
/**
|
||||
* 静态初始化器,由JVM来保证线程安全
|
||||
*/
|
||||
private static SeckillQueue queue = new SeckillQueue();
|
||||
}
|
||||
//单例队列
|
||||
public static SeckillQueue getMailQueue(){
|
||||
return SingletonHolder.queue;
|
||||
}
|
||||
/**
|
||||
* 生产入队
|
||||
* @param kill
|
||||
* @throws InterruptedException
|
||||
* add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue
|
||||
* put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。
|
||||
* offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立即返回。
|
||||
* offer(e, time, unit) 设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。
|
||||
*/
|
||||
public Boolean produce(SuccessKilled kill) throws InterruptedException {
|
||||
return blockingQueue.offer(kill);
|
||||
}
|
||||
//消费出队
|
||||
public SuccessKilled consume() throws InterruptedException {
|
||||
return blockingQueue.take();
|
||||
}
|
||||
// 获取队列大小
|
||||
public int size() {
|
||||
return blockingQueue.size();
|
||||
}
|
||||
}
|
||||
31
src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java
Normal file
31
src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.itstyle.seckill.queue.jvm;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.itstyle.seckill.common.entity.SuccessKilled;
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
/**
|
||||
* 消费秒杀队列
|
||||
* 创建者 科帮网
|
||||
* 创建时间 2018年4月3日
|
||||
*/
|
||||
@Component
|
||||
public class TaskRunner implements ApplicationRunner{
|
||||
|
||||
@Autowired
|
||||
private ISeckillService seckillService;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments var) throws Exception{
|
||||
while(true){
|
||||
//进程内队列
|
||||
SuccessKilled kill = SeckillQueue.getMailQueue().consume();
|
||||
if(kill!=null){
|
||||
seckillService.startSeckil(kill.getSeckillId(), kill.getUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.itstyle.seckill.queue.kafka;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
/**
|
||||
* 消费者 spring-kafka 2.0 + 依赖JDK8
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
@Component
|
||||
public class KafkaConsumer {
|
||||
@Autowired
|
||||
private ISeckillService seckillService;
|
||||
/**
|
||||
* 监听seckill主题,有消息就读取
|
||||
* @param message
|
||||
*/
|
||||
@KafkaListener(topics = {"seckill"})
|
||||
public void receiveMessage(String message){
|
||||
//收到通道的消息之后执行秒杀操作
|
||||
String[] array = message.split(";");
|
||||
seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.itstyle.seckill.queue.kafka;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
/**
|
||||
* 生产者
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
@Component
|
||||
public class KafkaSender {
|
||||
@Autowired
|
||||
private KafkaTemplate<String,String> kafkaTemplate;
|
||||
|
||||
/**
|
||||
* 发送消息到kafka
|
||||
*/
|
||||
public void sendChannelMess(String channel, String message){
|
||||
kafkaTemplate.send(channel,message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.itstyle.seckill.queue.redis;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
/**
|
||||
* 消费者
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
@Service
|
||||
public class RedisConsumer {
|
||||
|
||||
@Autowired
|
||||
private ISeckillService seckillService;
|
||||
|
||||
public void receiveMessage(String message) {
|
||||
//收到通道的消息之后执行秒杀操作(超卖)
|
||||
String[] array = message.split(";");
|
||||
seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.itstyle.seckill.queue.redis;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
/**
|
||||
* 生产者
|
||||
* @author 科帮网 By https://blog.52itstyle.com
|
||||
*/
|
||||
@Service
|
||||
public class RedisSender {
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
//向通道发送消息的方法
|
||||
public void sendChannelMess(String channel, String message) {
|
||||
stringRedisTemplate.convertAndSend(channel, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.itstyle.seckill.queue.redis;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
|
||||
@Configuration
|
||||
public class RedisSubListenerConfig {
|
||||
//初始化监听器
|
||||
@Bean
|
||||
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
|
||||
MessageListenerAdapter listenerAdapter) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.addMessageListener(listenerAdapter, new PatternTopic("seckill"));
|
||||
return container;
|
||||
}
|
||||
//利用反射来创建监听到消息之后的执行方法
|
||||
@Bean
|
||||
MessageListenerAdapter listenerAdapter(RedisConsumer redisReceiver) {
|
||||
return new MessageListenerAdapter(redisReceiver, "receiveMessage");
|
||||
}
|
||||
//使用默认的工厂初始化redis操作模板
|
||||
@Bean
|
||||
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
|
||||
return new StringRedisTemplate(connectionFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.itstyle.seckill.repository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Seckill;
|
||||
|
||||
public interface SeckillRepository extends JpaRepository<Seckill, Long> {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.itstyle.seckill.service;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
|
||||
public interface ISeckillDistributedService {
|
||||
|
||||
/**
|
||||
* 秒杀 一 单个商品
|
||||
* @param seckillId 秒杀商品ID
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilRedisLock(long seckillId,long userId);
|
||||
/**
|
||||
* 秒杀 一 单个商品
|
||||
* @param seckillId 秒杀商品ID
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilZksLock(long seckillId,long userId);
|
||||
|
||||
/**
|
||||
* 秒杀 二 多个商品
|
||||
* @param seckillId 秒杀商品ID
|
||||
* @param userId 用户ID
|
||||
* @param number 秒杀商品数量
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilLock(long seckillId,long userId,long number);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.itstyle.seckill.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.common.entity.Seckill;
|
||||
|
||||
public interface ISeckillService {
|
||||
|
||||
/**
|
||||
* 查询全部的秒杀记录
|
||||
* @return
|
||||
*/
|
||||
List<Seckill> getSeckillList();
|
||||
|
||||
/**
|
||||
* 查询单个秒杀记录
|
||||
* @param seckillId
|
||||
* @return
|
||||
*/
|
||||
Seckill getById(long seckillId);
|
||||
/**
|
||||
* 查询秒杀售卖商品
|
||||
* @param seckillId
|
||||
* @return
|
||||
*/
|
||||
Long getSeckillCount(long seckillId);
|
||||
/**
|
||||
* 删除秒杀售卖商品记录
|
||||
* @param seckillId
|
||||
* @return
|
||||
*/
|
||||
void deleteSeckill(long seckillId);
|
||||
|
||||
/**
|
||||
* 秒杀 一、会出现数量错误
|
||||
* @param seckillId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckil(long seckillId,long userId);
|
||||
|
||||
/**
|
||||
* 秒杀 二、程序锁
|
||||
* @param seckillId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilLock(long seckillId,long userId);
|
||||
|
||||
/**
|
||||
* 秒杀 二、数据库悲观锁
|
||||
* @param seckillId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilDBPCC_ONE(long seckillId,long userId);
|
||||
/**
|
||||
* 秒杀 三、数据库悲观锁
|
||||
* @param seckillId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilDBPCC_TWO(long seckillId,long userId);
|
||||
/**
|
||||
* 秒杀 三、数据库悲观锁
|
||||
* @param seckillId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckilDBOCC(long seckillId,long userId,long number);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.itstyle.seckill.service.impl;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.itstyle.seckill.common.dynamicquery.DynamicQuery;
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.common.entity.SuccessKilled;
|
||||
import com.itstyle.seckill.common.enums.SeckillStatEnum;
|
||||
import com.itstyle.seckill.distributedlock.redis.RedissLockUtil;
|
||||
import com.itstyle.seckill.distributedlock.zookeeper.ZkLockUtil;
|
||||
import com.itstyle.seckill.service.ISeckillDistributedService;
|
||||
@Service
|
||||
public class SeckillDistributedServiceImpl implements ISeckillDistributedService {
|
||||
|
||||
@Autowired
|
||||
private DynamicQuery dynamicQuery;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilRedisLock(long seckillId,long userId) {
|
||||
boolean res=false;
|
||||
try {
|
||||
//尝试获取锁,最多等待10秒,上锁以后10秒自动解锁(实际项目中推荐这种,以防出现死锁)
|
||||
res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 20, 10);
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long number = ((Number) object).longValue();
|
||||
if(number>0){
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally{
|
||||
if(res){//释放锁
|
||||
RedissLockUtil.unlock(seckillId+"");
|
||||
}
|
||||
}
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilZksLock(long seckillId, long userId) {
|
||||
try {
|
||||
ZkLockUtil.acquire();
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long number = ((Number) object).longValue();
|
||||
if(number>0){
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally{
|
||||
ZkLockUtil.release();
|
||||
}
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result startSeckilLock(long seckillId, long userId, long number) {
|
||||
boolean res=false;
|
||||
try {
|
||||
//尝试获取锁,最多等待3秒,上锁以后10秒自动解锁(实际项目中推荐这种,以防出现死锁)
|
||||
res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 3, 10);
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long count = ((Number) object).longValue();
|
||||
if(count>=number){
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
nativeSql = "UPDATE seckill SET number=number-? WHERE seckill_id=? AND number>0";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId});
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally{
|
||||
if(res){//释放锁
|
||||
RedissLockUtil.unlock(seckillId+"");
|
||||
}
|
||||
}
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.itstyle.seckill.service.impl;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.itstyle.seckill.common.dynamicquery.DynamicQuery;
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.common.entity.Seckill;
|
||||
import com.itstyle.seckill.common.entity.SuccessKilled;
|
||||
import com.itstyle.seckill.common.enums.SeckillStatEnum;
|
||||
import com.itstyle.seckill.repository.SeckillRepository;
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
@Service
|
||||
public class SeckillServiceImpl implements ISeckillService {
|
||||
/**
|
||||
* 思考:为什么不用synchronized
|
||||
* service 默认是单例的,并发下lock只有一个实例
|
||||
*/
|
||||
private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁
|
||||
|
||||
@Autowired
|
||||
private DynamicQuery dynamicQuery;
|
||||
@Autowired
|
||||
private SeckillRepository seckillRepository;
|
||||
|
||||
@Override
|
||||
public List<Seckill> getSeckillList() {
|
||||
return seckillRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seckill getById(long seckillId) {
|
||||
return seckillRepository.findOne(seckillId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getSeckillCount(long seckillId) {
|
||||
String nativeSql = "SELECT count(*) FROM success_killed WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
return ((Number) object).longValue();
|
||||
}
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteSeckill(long seckillId) {
|
||||
String nativeSql = "DELETE FROM success_killed WHERE seckill_id=?";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
nativeSql = "UPDATE seckill SET number =100 WHERE seckill_id=?";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
}
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckil(long seckillId,long userId) {
|
||||
//校验库存
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long number = ((Number) object).longValue();
|
||||
if(number>0){
|
||||
//扣库存
|
||||
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
//创建订单
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
//支付
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilLock(long seckillId, long userId) {
|
||||
try {
|
||||
lock.lock();
|
||||
//这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long number = ((Number) object).longValue();
|
||||
if(number>0){
|
||||
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState(Short.parseShort(number+""));
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilDBPCC_ONE(long seckillId, long userId) {
|
||||
//单用户抢购一件商品或者多件都没有问题
|
||||
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=? FOR UPDATE";
|
||||
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
|
||||
Long number = ((Number) object).longValue();
|
||||
if(number>0){
|
||||
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?";
|
||||
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilDBPCC_TWO(long seckillId, long userId) {
|
||||
//单用户抢购一件商品没有问题、但是抢购多件商品不建议这种写法
|
||||
String nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";//UPDATE锁表
|
||||
int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
|
||||
if(count>0){
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckilDBOCC(long seckillId, long userId, long number) {
|
||||
Seckill kill = seckillRepository.findOne(seckillId);
|
||||
if(kill.getNumber()>0){
|
||||
//乐观锁
|
||||
String nativeSql = "UPDATE seckill SET number=number-?,version=version+1 WHERE seckill_id=? AND version = ?";
|
||||
int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId,kill.getVersion()});
|
||||
if(count>0){
|
||||
SuccessKilled killed = new SuccessKilled();
|
||||
killed.setSeckillId(seckillId);
|
||||
killed.setUserId(userId);
|
||||
killed.setState((short)0);
|
||||
killed.setCreateTime(new Timestamp(new Date().getTime()));
|
||||
dynamicQuery.save(killed);
|
||||
return Result.ok(SeckillStatEnum.SUCCESS);
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
}else{
|
||||
return Result.error(SeckillStatEnum.END);
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/main/java/com/itstyle/seckill/web/SeckillController.java
Normal file
203
src/main/java/com/itstyle/seckill/web/SeckillController.java
Normal file
@@ -0,0 +1,203 @@
|
||||
package com.itstyle.seckill.web;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.common.entity.SuccessKilled;
|
||||
import com.itstyle.seckill.queue.jvm.SeckillQueue;
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
@Api(tags ="秒杀")
|
||||
@RestController
|
||||
@RequestMapping("/seckill")
|
||||
public class SeckillController {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(SeckillController.class);
|
||||
|
||||
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
|
||||
//调整队列数 拒绝服务
|
||||
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(1000));
|
||||
|
||||
@Autowired
|
||||
private ISeckillService seckillService;
|
||||
|
||||
@ApiOperation(value="秒杀一(最low实现)",nickname="科帮网")
|
||||
@PostMapping("/start")
|
||||
public Result start(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀一(会出现超卖)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillService.startSeckil(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀二(程序锁)",nickname="科帮网")
|
||||
@PostMapping("/startLock")
|
||||
public Result startLock(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀二(正常)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillService.startSeckilLock(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀三(数据库悲观锁)",nickname="科帮网")
|
||||
@PostMapping("/startDBPCC_ONE")
|
||||
public Result startDBPCC_ONE(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀三(正常)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillService.startSeckilDBPCC_ONE(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀四(数据库悲观锁)",nickname="科帮网")
|
||||
@PostMapping("/startDPCC_TWO")
|
||||
public Result startDPCC_TWO(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀四(正常、数据库锁最优实现)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillService.startSeckilDBPCC_TWO(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀五(数据库乐观锁)",nickname="科帮网")
|
||||
@PostMapping("/startDBOCC")
|
||||
public Result startDBOCC(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀五(正常、数据库锁最优实现)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillService.startSeckilDBOCC(killId, userId,4);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀六(进程内队列)",nickname="科帮网")
|
||||
@PostMapping("/startQueue")
|
||||
public Result startQueue(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀六(正常)");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SuccessKilled kill = new SuccessKilled();
|
||||
kill.setSeckillId(killId);
|
||||
kill.setUserId(userId);
|
||||
try {
|
||||
Boolean flag = SeckillQueue.getMailQueue().produce(kill);
|
||||
if(flag){
|
||||
LOGGER.info("用户:{}{}",kill.getUserId(),"秒杀成功");
|
||||
}else{
|
||||
LOGGER.info("用户:{}{}",userId,"秒杀失败");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.info("用户:{}{}",userId,"秒杀失败");
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.itstyle.seckill.web;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.queue.kafka.KafkaSender;
|
||||
import com.itstyle.seckill.queue.redis.RedisSender;
|
||||
import com.itstyle.seckill.service.ISeckillDistributedService;
|
||||
import com.itstyle.seckill.service.ISeckillService;
|
||||
@Api(tags ="分布式秒杀")
|
||||
@RestController
|
||||
@RequestMapping("/seckillDistributed")
|
||||
public class SeckillDistributedController {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(SeckillDistributedController.class);
|
||||
|
||||
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
|
||||
//调整队列数 拒绝服务
|
||||
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(10000));
|
||||
|
||||
@Autowired
|
||||
private ISeckillService seckillService;
|
||||
@Autowired
|
||||
private ISeckillDistributedService seckillDistributedService;
|
||||
@Autowired
|
||||
private RedisSender redisSender;
|
||||
@Autowired
|
||||
private KafkaSender kafkaSender;
|
||||
|
||||
@ApiOperation(value="秒杀一(Rediss分布式锁)",nickname="科帮网")
|
||||
@PostMapping("/startRedisLock")
|
||||
public Result startRedisLock(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀一");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillDistributedService.startSeckilRedisLock(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀二(zookeeper分布式锁)",nickname="科帮网")
|
||||
@PostMapping("/startZkLock")
|
||||
public Result startZkLock(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀二");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Result result = seckillDistributedService.startSeckilZksLock(killId, userId);
|
||||
LOGGER.info("用户:{}{}",userId,result.get("msg"));
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀三(Redis分布式队列-订阅监听)",nickname="科帮网")
|
||||
@PostMapping("/startRedisQueue")
|
||||
public Result startRedisQueue(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀三");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//思考如何返回给用户信息ws
|
||||
redisSender.sendChannelMess("seckill",killId+";"+userId);
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
@ApiOperation(value="秒杀四(Kafka分布式队列)",nickname="科帮网")
|
||||
@PostMapping("/startKafkaQueue")
|
||||
public Result startKafkaQueue(long seckillId){
|
||||
seckillService.deleteSeckill(seckillId);
|
||||
final long killId = seckillId;
|
||||
LOGGER.info("开始秒杀四");
|
||||
for(int i=0;i<1000;i++){
|
||||
final long userId = i;
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//思考如何返回给用户信息ws
|
||||
kafkaSender.sendChannelMess("seckill",killId+";"+userId);
|
||||
}
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
Long seckillCount = seckillService.getSeckillCount(seckillId);
|
||||
LOGGER.info("一共秒杀出{}件商品",seckillCount);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
82
src/main/resources/application.properties
Normal file
82
src/main/resources/application.properties
Normal file
@@ -0,0 +1,82 @@
|
||||
# \u9879\u76eecontextPath \u79d1\u5e2e\u7f51https://blog.52itstyle.com
|
||||
server.context-path=/seckill
|
||||
# \u670d\u52a1\u7aef\u53e3
|
||||
server.port=8080
|
||||
# session\u6700\u5927\u8d85\u65f6\u65f6\u95f4(\u5206\u949f)\uff0c\u9ed8\u8ba4\u4e3a30
|
||||
server.session-timeout=60
|
||||
# tomcat\u6700\u5927\u7ebf\u7a0b\u6570\uff0c\u9ed8\u8ba4\u4e3a200
|
||||
server.tomcat.max-threads=100
|
||||
# tomcat\u7684URI\u7f16\u7801
|
||||
server.tomcat.uri-encoding=UTF-8
|
||||
|
||||
#spring boot\u4ece\u63a7\u5236\u53f0\u6253\u5370\u51fa\u6765\u7684\u65e5\u5fd7\u7ea7\u522b\u53ea\u6709ERROR, WARN \u8fd8\u6709INFO\uff0c\u5982\u679c\u4f60\u60f3\u8981\u6253\u5370debug\u7ea7\u522b\u7684\u65e5\u5fd7
|
||||
#debug=true
|
||||
logging.level.root=INFO
|
||||
|
||||
spring.thymeleaf.mode=LEGACYHTML5
|
||||
#dev tools
|
||||
spring.devtools.livereload.enabled=true
|
||||
spring.thymeleaf.cache=false
|
||||
spring.thymeleaf.cache-period=0
|
||||
spring.thymeleaf.template.cache=false
|
||||
# \u9759\u6001\u6587\u4ef6\u8bf7\u6c42\u5339\u914d\u65b9\u5f0f
|
||||
spring.mvc.static-path-pattern=/**
|
||||
|
||||
#\u6ce8\u610f\u4e2d\u6587\u4e71\u7801
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/seckill?characterEncoding=utf-8&useSSL=false
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=123456
|
||||
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
# Specify the DBMS
|
||||
spring.jpa.database = MYSQL
|
||||
# Show or not log for each sql query
|
||||
spring.jpa.show-sql = false
|
||||
# DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database, "none" otherwise.
|
||||
spring.jpa.hibernate.ddl-auto = update
|
||||
# Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate 5.
|
||||
spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy
|
||||
# stripped before adding them to the entity manager)
|
||||
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
|
||||
|
||||
# Redis
|
||||
# \u6570\u636e\u5e93\u7d22\u5f15\uff08\u9ed8\u8ba4\u4e3a0\uff09
|
||||
spring.redis.database=0
|
||||
# \u670d\u52a1\u5668\u5730\u5740 \u53d8\u66f4\u4e3a\u81ea\u5df1\u7684
|
||||
spring.redis.host=192.168.1.180
|
||||
# \u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3
|
||||
spring.redis.port=6379
|
||||
# \u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09\u5982\u679c\u6709\u53d8\u66f4\u4e3a\u81ea\u5df1\u7684
|
||||
spring.redis.password=6347097
|
||||
# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
|
||||
spring.redis.pool.max-active=8
|
||||
# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
|
||||
spring.redis.pool.max-wait=-1
|
||||
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5
|
||||
spring.redis.pool.max-idle=8
|
||||
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5
|
||||
spring.redis.pool.min-idle=0
|
||||
# \u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff08\u6beb\u79d2\uff09
|
||||
spring.redis.timeout=30000
|
||||
|
||||
|
||||
# redisson lock
|
||||
redisson.address=redis://192.168.1.180:6379
|
||||
redisson.password=6347097
|
||||
|
||||
|
||||
#kafka\u76f8\u5173\u914d\u7f6e
|
||||
spring.kafka.bootstrap-servers=192.168.1.180:9092
|
||||
#\u8bbe\u7f6e\u4e00\u4e2a\u9ed8\u8ba4\u7ec4
|
||||
spring.kafka.consumer.group-id=0
|
||||
#key-value\u5e8f\u5217\u5316\u53cd\u5e8f\u5217\u5316
|
||||
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
#\u6bcf\u6b21\u6279\u91cf\u53d1\u9001\u6d88\u606f\u7684\u6570\u91cf
|
||||
spring.kafka.producer.batch-size=65536
|
||||
spring.kafka.producer.buffer-memory=524288
|
||||
|
||||
|
||||
#zookeeper.address
|
||||
zookeeper.address = 192.168.1.180:2181
|
||||
82
src/main/resources/logback-spring.xml
Normal file
82
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- scan 配置文件如果发生改变,将会被重新加载 scanPeriod 检测间隔时间 BY 科帮网 小柒2012 欢迎关注博客https://blog.52itstyle.com-->
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<contextName>spring-boot-seckill</contextName>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<!-- 普通日志 -->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>log/spring-boot-seckill-info.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志命名:单个文件大于128MB 按照时间+自增i 生成log文件 -->
|
||||
<fileNamePattern>log/spring-boot-seckill-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>128MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
<!-- 最大保存时间:30天-->
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<append>true</append>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>info</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- 错误日志 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>log/spring-boot-seckill-error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志命名:单个文件大于2MB 按照时间+自增i 生成log文件 -->
|
||||
<fileNamePattern>log/spring-boot-seckill-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>2MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
<!-- 最大保存时间:180天-->
|
||||
<maxHistory>180</maxHistory>
|
||||
</rollingPolicy>
|
||||
<append>true</append>
|
||||
<!-- 日志格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!-- 日志级别过滤器 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
<!-- 匹配时的操作:接收(记录) -->
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- 控制台 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- 日志格式 -->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- additivity 避免执行2次 -->
|
||||
<logger name="com.itstyle" level="INFO" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</logger>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="INFO_FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
55
src/main/resources/sql/seckill.sql
Normal file
55
src/main/resources/sql/seckill.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
SQLyog 企业版 - MySQL GUI v8.14
|
||||
MySQL - 5.7.17-log : Database - seckill
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
|
||||
/*!40101 SET SQL_MODE=''*/;
|
||||
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
/*Table structure for table `seckill` */
|
||||
|
||||
DROP TABLE IF EXISTS `seckill`;
|
||||
|
||||
CREATE TABLE `seckill` (
|
||||
`seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
|
||||
`name` varchar(120) NOT NULL COMMENT '商品名称',
|
||||
`number` int(11) NOT NULL COMMENT '库存数量',
|
||||
`start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀开启时间',
|
||||
`end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀结束时间',
|
||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`version` int(11) NOT NULL COMMENT '版本号',
|
||||
PRIMARY KEY (`seckill_id`),
|
||||
KEY `idx_start_time` (`start_time`),
|
||||
KEY `idx_end_time` (`end_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';
|
||||
|
||||
/*Data for the table `seckill` */
|
||||
|
||||
insert into `seckill`(`seckill_id`,`name`,`number`,`start_time`,`end_time`,`create_time`,`version`) values (1000,'1000元秒杀iphone8',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1001,'500元秒杀ipad2',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1002,'300元秒杀小米4',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1003,'200元秒杀红米note',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0);
|
||||
|
||||
/*Table structure for table `success_killed` */
|
||||
|
||||
DROP TABLE IF EXISTS `success_killed`;
|
||||
|
||||
CREATE TABLE `success_killed` (
|
||||
`seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品id',
|
||||
`user_id` bigint(20) NOT NULL COMMENT '用户Id',
|
||||
`state` tinyint(4) NOT NULL COMMENT '状态标示:-1指无效,0指成功,1指已付款',
|
||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`seckill_id`,`user_id`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
|
||||
|
||||
/*Data for the table `success_killed` */
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
7
src/main/webapp/WEB-INF/web.xml
Normal file
7
src/main/webapp/WEB-INF/web.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE web-app PUBLIC
|
||||
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
|
||||
"http://java.sun.com/dtd/web-app_2_3.dtd" >
|
||||
|
||||
<web-app>
|
||||
<display-name>Archetype Created Web Application</display-name>
|
||||
</web-app>
|
||||
5
src/main/webapp/index.jsp
Normal file
5
src/main/webapp/index.jsp
Normal file
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
<h2>Hello World!</h2>
|
||||
</body>
|
||||
</html>
|
||||
82
target/classes/application.properties
Normal file
82
target/classes/application.properties
Normal file
@@ -0,0 +1,82 @@
|
||||
# \u9879\u76eecontextPath \u79d1\u5e2e\u7f51https://blog.52itstyle.com
|
||||
server.context-path=/seckill
|
||||
# \u670d\u52a1\u7aef\u53e3
|
||||
server.port=8080
|
||||
# session\u6700\u5927\u8d85\u65f6\u65f6\u95f4(\u5206\u949f)\uff0c\u9ed8\u8ba4\u4e3a30
|
||||
server.session-timeout=60
|
||||
# tomcat\u6700\u5927\u7ebf\u7a0b\u6570\uff0c\u9ed8\u8ba4\u4e3a200
|
||||
server.tomcat.max-threads=100
|
||||
# tomcat\u7684URI\u7f16\u7801
|
||||
server.tomcat.uri-encoding=UTF-8
|
||||
|
||||
#spring boot\u4ece\u63a7\u5236\u53f0\u6253\u5370\u51fa\u6765\u7684\u65e5\u5fd7\u7ea7\u522b\u53ea\u6709ERROR, WARN \u8fd8\u6709INFO\uff0c\u5982\u679c\u4f60\u60f3\u8981\u6253\u5370debug\u7ea7\u522b\u7684\u65e5\u5fd7
|
||||
#debug=true
|
||||
logging.level.root=INFO
|
||||
|
||||
spring.thymeleaf.mode=LEGACYHTML5
|
||||
#dev tools
|
||||
spring.devtools.livereload.enabled=true
|
||||
spring.thymeleaf.cache=false
|
||||
spring.thymeleaf.cache-period=0
|
||||
spring.thymeleaf.template.cache=false
|
||||
# \u9759\u6001\u6587\u4ef6\u8bf7\u6c42\u5339\u914d\u65b9\u5f0f
|
||||
spring.mvc.static-path-pattern=/**
|
||||
|
||||
#\u6ce8\u610f\u4e2d\u6587\u4e71\u7801
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/seckill?characterEncoding=utf-8&useSSL=false
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=123456
|
||||
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
# Specify the DBMS
|
||||
spring.jpa.database = MYSQL
|
||||
# Show or not log for each sql query
|
||||
spring.jpa.show-sql = false
|
||||
# DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database, "none" otherwise.
|
||||
spring.jpa.hibernate.ddl-auto = update
|
||||
# Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate 5.
|
||||
spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy
|
||||
# stripped before adding them to the entity manager)
|
||||
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
|
||||
|
||||
# Redis
|
||||
# \u6570\u636e\u5e93\u7d22\u5f15\uff08\u9ed8\u8ba4\u4e3a0\uff09
|
||||
spring.redis.database=0
|
||||
# \u670d\u52a1\u5668\u5730\u5740 \u53d8\u66f4\u4e3a\u81ea\u5df1\u7684
|
||||
spring.redis.host=192.168.1.180
|
||||
# \u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3
|
||||
spring.redis.port=6379
|
||||
# \u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09\u5982\u679c\u6709\u53d8\u66f4\u4e3a\u81ea\u5df1\u7684
|
||||
spring.redis.password=6347097
|
||||
# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
|
||||
spring.redis.pool.max-active=8
|
||||
# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
|
||||
spring.redis.pool.max-wait=-1
|
||||
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5
|
||||
spring.redis.pool.max-idle=8
|
||||
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5
|
||||
spring.redis.pool.min-idle=0
|
||||
# \u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff08\u6beb\u79d2\uff09
|
||||
spring.redis.timeout=30000
|
||||
|
||||
|
||||
# redisson lock
|
||||
redisson.address=redis://192.168.1.180:6379
|
||||
redisson.password=6347097
|
||||
|
||||
|
||||
#kafka\u76f8\u5173\u914d\u7f6e
|
||||
spring.kafka.bootstrap-servers=192.168.1.180:9092
|
||||
#\u8bbe\u7f6e\u4e00\u4e2a\u9ed8\u8ba4\u7ec4
|
||||
spring.kafka.consumer.group-id=0
|
||||
#key-value\u5e8f\u5217\u5316\u53cd\u5e8f\u5217\u5316
|
||||
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
#\u6bcf\u6b21\u6279\u91cf\u53d1\u9001\u6d88\u606f\u7684\u6570\u91cf
|
||||
spring.kafka.producer.batch-size=65536
|
||||
spring.kafka.producer.buffer-memory=524288
|
||||
|
||||
|
||||
#zookeeper.address
|
||||
zookeeper.address = 192.168.1.180:2181
|
||||
82
target/classes/logback-spring.xml
Normal file
82
target/classes/logback-spring.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- scan 配置文件如果发生改变,将会被重新加载 scanPeriod 检测间隔时间 BY 科帮网 小柒2012 欢迎关注博客https://blog.52itstyle.com-->
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<contextName>spring-boot-seckill</contextName>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<!-- 普通日志 -->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>log/spring-boot-seckill-info.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志命名:单个文件大于128MB 按照时间+自增i 生成log文件 -->
|
||||
<fileNamePattern>log/spring-boot-seckill-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>128MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
<!-- 最大保存时间:30天-->
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<append>true</append>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>info</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- 错误日志 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>log/spring-boot-seckill-error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志命名:单个文件大于2MB 按照时间+自增i 生成log文件 -->
|
||||
<fileNamePattern>log/spring-boot-seckill-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>2MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
<!-- 最大保存时间:180天-->
|
||||
<maxHistory>180</maxHistory>
|
||||
</rollingPolicy>
|
||||
<append>true</append>
|
||||
<!-- 日志格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!-- 日志级别过滤器 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
<!-- 匹配时的操作:接收(记录) -->
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- 控制台 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- 日志格式 -->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- additivity 避免执行2次 -->
|
||||
<logger name="com.itstyle" level="INFO" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</logger>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="INFO_FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
55
target/classes/sql/seckill.sql
Normal file
55
target/classes/sql/seckill.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
SQLyog 企业版 - MySQL GUI v8.14
|
||||
MySQL - 5.7.17-log : Database - seckill
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
|
||||
/*!40101 SET SQL_MODE=''*/;
|
||||
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
/*Table structure for table `seckill` */
|
||||
|
||||
DROP TABLE IF EXISTS `seckill`;
|
||||
|
||||
CREATE TABLE `seckill` (
|
||||
`seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
|
||||
`name` varchar(120) NOT NULL COMMENT '商品名称',
|
||||
`number` int(11) NOT NULL COMMENT '库存数量',
|
||||
`start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀开启时间',
|
||||
`end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀结束时间',
|
||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`version` int(11) NOT NULL COMMENT '版本号',
|
||||
PRIMARY KEY (`seckill_id`),
|
||||
KEY `idx_start_time` (`start_time`),
|
||||
KEY `idx_end_time` (`end_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';
|
||||
|
||||
/*Data for the table `seckill` */
|
||||
|
||||
insert into `seckill`(`seckill_id`,`name`,`number`,`start_time`,`end_time`,`create_time`,`version`) values (1000,'1000元秒杀iphone8',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1001,'500元秒杀ipad2',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1002,'300元秒杀小米4',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1003,'200元秒杀红米note',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0);
|
||||
|
||||
/*Table structure for table `success_killed` */
|
||||
|
||||
DROP TABLE IF EXISTS `success_killed`;
|
||||
|
||||
CREATE TABLE `success_killed` (
|
||||
`seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品id',
|
||||
`user_id` bigint(20) NOT NULL COMMENT '用户Id',
|
||||
`state` tinyint(4) NOT NULL COMMENT '状态标示:-1指无效,0指成功,1指已付款',
|
||||
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`seckill_id`,`user_id`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
|
||||
|
||||
/*Data for the table `success_killed` */
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
Reference in New Issue
Block a user