├── .gitignore ├── mysql-data-generator ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ └── bootstrap.yml │ │ └── java │ │ │ └── com │ │ │ └── wuda │ │ │ └── tester │ │ │ └── mysql │ │ │ ├── cli │ │ │ ├── package-info.java │ │ │ ├── CliArgsRegulator.java │ │ │ ├── CliArgs.java │ │ │ └── CliOptionUtils.java │ │ │ ├── config │ │ │ ├── package-info.java │ │ │ └── DatabaseConfiguration.java │ │ │ ├── statistic │ │ │ ├── package-info.java │ │ │ └── DataGenerateStat.java │ │ │ ├── generate │ │ │ ├── package-info.java │ │ │ ├── AlreadyStoppedException.java │ │ │ ├── DataSet.java │ │ │ ├── DataGenerateStopPolicy.java │ │ │ ├── DefaultDataGeneratorStopPolicy.java │ │ │ ├── FoundationBasedDataGenerator.java │ │ │ ├── DataGenerator.java │ │ │ └── FoundationBasedDataSet.java │ │ │ ├── TableName.java │ │ │ ├── SpringApplicationContextHolder.java │ │ │ └── Bootstrap.java │ └── test │ │ └── java │ │ └── com │ │ └── wuda │ │ └── tester │ │ └── mysql │ │ └── cli │ │ └── CliOptionUtilsTest.java └── pom.xml ├── mysql-tester-commons ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── wuda │ └── tester │ └── mysql │ └── commons │ ├── keygen │ ├── KenGenTimeBackwardsException.java │ ├── KeyGenExceedMaxValueException.java │ └── KeyGeneratorSnowflake.java │ └── utils │ ├── StringUtilsExt.java │ └── RandomUtilsExt.java ├── pom.xml ├── README.md └── mysql_tester.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | target/ 4 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: debug -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 命令行工具. 3 | * 4 | * @author wuda 5 | */ 6 | package com.wuda.tester.mysql.cli; -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 管理配置信息. 3 | * 4 | * @author wuda 5 | */ 6 | package com.wuda.tester.mysql.config; -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/statistic/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 统计信息.比如每个表插入了多少数据,失败次数等等. 3 | * 4 | * @author wuda 5 | */ 6 | package com.wuda.tester.mysql.statistic; -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 主要处理数据生成.{@link com.wuda.tester.mysql.generate.DataFactory}类用于为每个表生成Insert into ...Values语句, 3 | * {@link com.wuda.tester.mysql.generate.DataGenerator}类用于组织生成数据的完整逻辑. 4 | * 5 | * @author wuda 6 | */ 7 | package com.wuda.tester.mysql.generate; -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/AlreadyStoppedException.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | /** 4 | * 已经停止. 5 | * 6 | * @author wuda 7 | */ 8 | public class AlreadyStoppedException extends Exception { 9 | 10 | /** 11 | * serialVersionUID. 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | } 15 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/TableName.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql; 2 | 3 | /** 4 | * 表名. 5 | */ 6 | public enum TableName { 7 | USER, 8 | USER_ACCOUNT, 9 | INDIVIDUAL_USER_GENERAL, 10 | USER_EMAIL, 11 | USER_PHONE, 12 | STORE, 13 | STORE_GENERAL, 14 | STORE_USER_RELATIONSHIP, 15 | ITEM, 16 | ITEM_GENERAL, 17 | ITEM_VARIATION, 18 | ITEM_DESCRIPTION, 19 | EMAIL, 20 | PHONE, 21 | PROPERTY_KEY, 22 | PROPERTY_VALUE; 23 | } 24 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataSet.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.tester.mysql.cli.CliArgs; 4 | 5 | /** 6 | * 数据集,先在内存中准备好数据,然后这些数据再被插入数据库中. 7 | * 不是一次性把所有数据都生成了,如果要生成5000万数据,那么内存肯定溢出, 8 | * 可以多次生成{@link DataSet},只要最后总的数据量满足即可. 9 | * 通常由{@link com.wuda.tester.mysql.generate.DataGenerator.Producer}生成数据集,然后这些数据 10 | * 再由{@link com.wuda.tester.mysql.generate.DataGenerator.Consumer}消费,也就是插入数据库中. 11 | * 12 | * @author wuda 13 | */ 14 | public interface DataSet { 15 | 16 | /** 17 | * 准备好静态数据,这些数据将被插入到数据库中. 18 | * 19 | * @param cliArgs 包含了数据量参数,这样才知道每次生成多少数据量比较合适 20 | */ 21 | void prepare(CliArgs cliArgs); 22 | } 23 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/SpringApplicationContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | /** 6 | * spring application context. 7 | * 8 | * @author wuda 9 | */ 10 | public class SpringApplicationContextHolder { 11 | 12 | private static ApplicationContext applicationContext; 13 | 14 | public static void setApplicationContext(ApplicationContext applicationContext) { 15 | SpringApplicationContextHolder.applicationContext = applicationContext; 16 | } 17 | 18 | public static ApplicationContext getApplicationContext() { 19 | return SpringApplicationContextHolder.applicationContext; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataGenerateStopPolicy.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.tester.mysql.statistic.DataGenerateStat; 4 | 5 | /** 6 | * 停止生成数据的策略.尤其是全量数据生成时,不会一直无限制生成, 需要一个停止策略,比如某一个表数据量达到100W条记录时停止. 7 | * 8 | * @author wuda 9 | */ 10 | public interface DataGenerateStopPolicy { 11 | 12 | /** 13 | * 根据统计信息决定是否可以停止了. 14 | * 15 | * @param stat 统计信息 16 | * @return true-如果可以停止了 17 | */ 18 | boolean stop(DataGenerateStat stat); 19 | 20 | /** 21 | * 当数据生成任务退出时,即{@link #stop(DataGenerateStat)}=true时, 22 | * 给出具体的理由,为什么会做出停止的原因. 23 | * 24 | * @return 停止的原因 25 | */ 26 | String getStopMessage(); 27 | } 28 | -------------------------------------------------------------------------------- /mysql-tester-commons/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mysql-tester 5 | io.github.wuda0112 6 | 1.0.5-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | mysql-tester-commons 11 | 12 | 13 | 14 | org.apache.commons 15 | commons-lang3 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mysql-data-generator/src/test/java/com/wuda/tester/mysql/cli/CliOptionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.cli; 2 | 3 | import org.junit.Test; 4 | 5 | public class CliOptionUtilsTest { 6 | 7 | @Test 8 | public void parser() { 9 | // String[] args = new String[]{"-h"}; 10 | String[] args = new String[]{"--mysql-url=jdbc:mysql://localhost:3306/?serverTimezone=UTC", 11 | "--mysql-username=test", 12 | "--mysql-password=123456", 13 | "--mysql-max-connection=8", 14 | "--thread=30", 15 | "--user-count=100", 16 | "--max-item-per-user=4"}; 17 | CliArgs cliArgs = CliOptionUtils.parser(CliOptionUtils.getOptions(), args); 18 | System.out.println(cliArgs); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KenGenTimeBackwardsException.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.commons.keygen; 2 | 3 | /** 4 | * 时间被向后调整异常. 5 | * 6 | * @author wuda 7 | * @version 1.0 8 | */ 9 | public class KenGenTimeBackwardsException extends RuntimeException { 10 | 11 | /** 12 | * Constructs a new exception with the specified detail message. The 13 | * cause is not initialized, and may subsequently be initialized by 14 | * a call to {@link #initCause}. 15 | * 16 | * @param message 17 | * the detail message. The detail message is saved for 18 | * later retrieval by the {@link #getMessage()} method. 19 | */ 20 | public KenGenTimeBackwardsException(String message) { 21 | super(message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KeyGenExceedMaxValueException.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.commons.keygen; 2 | 3 | /** 4 | * 超过最大可生成的key异常. 5 | * 6 | * @author wuda 7 | * @version 1.0 8 | */ 9 | public class KeyGenExceedMaxValueException extends RuntimeException { 10 | 11 | /** 12 | * Constructs a new exception with the specified detail message. The 13 | * cause is not initialized, and may subsequently be initialized by 14 | * a call to {@link #initCause}. 15 | * 16 | * @param message 17 | * the detail message. The detail message is saved for 18 | * later retrieval by the {@link #getMessage()} method. 19 | */ 20 | public KeyGenExceedMaxValueException(String message) { 21 | super(message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | io.github.wuda0112 6 | yhan-spring-boot-based-parent 7 | 1.0.2 8 | 9 | 10 | mysql-tester 11 | 1.0.5-SNAPSHOT 12 | pom 13 | 14 | mysql-tester 15 | 16 | 17 | 18 | Apache Software License 2.0 19 | http://www.apache.org/licenses/LICENSE-2.0 20 | repo 21 | 22 | 23 | 24 | 25 | https://github.com/wuda0112/mysql-tester 26 | scm:git:git@github.com:wuda0112/mysql-tester.git 27 | scm:git:git@github.com:wuda0112/mysql-tester.git 28 | HEAD 29 | 30 | 31 | 32 | 33 | wuda 34 | wuda0112@gmail.com 35 | 36 | 37 | 38 | 39 | 1.0.0 40 | 41 | 42 | 43 | mysql-data-generator 44 | mysql-tester-commons 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DefaultDataGeneratorStopPolicy.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.tester.mysql.TableName; 4 | import com.wuda.tester.mysql.cli.CliArgs; 5 | import com.wuda.tester.mysql.statistic.DataGenerateStat; 6 | 7 | /** 8 | * 全量数据生成的停止策略. 9 | * 10 | * @author wuda 11 | */ 12 | public class DefaultDataGeneratorStopPolicy implements DataGenerateStopPolicy { 13 | 14 | /** 15 | * 退出时,给出具体的描述信息. 16 | */ 17 | private String stopMessage; 18 | 19 | /** 20 | * 命令行参数. 21 | */ 22 | private CliArgs cliArgs; 23 | 24 | /** 25 | * 生成实例. 26 | * 27 | * @param cliArgs 命令行参数 28 | */ 29 | public DefaultDataGeneratorStopPolicy(CliArgs cliArgs) { 30 | this.cliArgs = cliArgs; 31 | } 32 | 33 | @Override 34 | public boolean stop(DataGenerateStat stat) { 35 | if (stat.getInsertedCount(TableName.USER) >= cliArgs.getUserCount()) { 36 | stopMessage = "数据生成任务成功完成"; 37 | return true; 38 | } else if (stat.getTotalTaskCount() > cliArgs.getThread() && stat.getFailureRate() > 0.1F) { 39 | stopMessage = "thread=" + cliArgs.getThread() 40 | + ",totalTaskCount=" + stat.getTotalTaskCount() 41 | + ",failureTaskCount=" + stat.getFailureTaskCount() 42 | + ",失败率太高!"; 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | /** 49 | * 当数据生成任务退出时,即{@link #stop(DataGenerateStat)}=true时, 50 | * 给出具体的理由,为什么会做出停止的原因. 51 | * 52 | * @return 停止的原因 53 | */ 54 | public String getStopMessage() { 55 | return stopMessage; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/Bootstrap.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql; 2 | 3 | import com.wuda.tester.mysql.cli.CliArgs; 4 | import com.wuda.tester.mysql.cli.CliArgsRegulator; 5 | import com.wuda.tester.mysql.cli.CliOptionUtils; 6 | import com.wuda.tester.mysql.config.DatabaseConfiguration; 7 | import com.wuda.tester.mysql.generate.DataGenerator; 8 | import com.wuda.tester.mysql.generate.FoundationBasedDataGenerator; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.SpringApplication; 12 | import org.springframework.boot.autoconfigure.SpringBootApplication; 13 | import org.springframework.context.ConfigurableApplicationContext; 14 | 15 | import javax.sql.DataSource; 16 | 17 | /** 18 | * 程序启动类.以SpringBoot项目方式启动. 19 | * 20 | * @author wuda 21 | */ 22 | @SpringBootApplication 23 | public class Bootstrap { 24 | 25 | /** 26 | * logger. 27 | */ 28 | private static Logger logger = LoggerFactory.getLogger(Bootstrap.class); 29 | 30 | public static void main(String[] args) { 31 | // 解析命令行参数 32 | CliArgs cliArgs = CliOptionUtils.parser(CliOptionUtils.getOptions(), args); 33 | cliArgs.validate(); 34 | CliArgsRegulator.regulate(cliArgs); 35 | // spring boot 36 | ConfigurableApplicationContext context = SpringApplication.run(Bootstrap.class, args); 37 | SpringApplicationContextHolder.setApplicationContext(context); 38 | // 使用命令行传过来的数据库参数 39 | DatabaseConfiguration.applyArgs(context.getBean(DataSource.class), cliArgs); 40 | DataSource dataSource = SpringApplicationContextHolder.getApplicationContext().getBean(DataSource.class); 41 | 42 | // 全量数据生成器 43 | DataGenerator generator = new FoundationBasedDataGenerator(cliArgs, dataSource); 44 | generator.startup(); 45 | System.out.println("---------数据生成正在进行中......---------"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliArgsRegulator.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.cli; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * 用于调节命令行参数,避免传入的命令行参数不合理. 8 | * 9 | * @author wuda 10 | */ 11 | public class CliArgsRegulator { 12 | 13 | private static Logger logger = LoggerFactory.getLogger(CliArgsRegulator.class); 14 | 15 | /** 16 | * 调节,校准参数. 17 | * 18 | * @param cliArgs 从命令行传递的参数 19 | */ 20 | public static void regulate(CliArgs cliArgs) { 21 | regulateMaxItemPerUser(cliArgs); 22 | regulateThreadCount(cliArgs); 23 | } 24 | 25 | /** 26 | * 调节线程数. 27 | */ 28 | public static void regulateThreadCount(CliArgs cliArgs) { 29 | int expectedThreadCount = cliArgs.getThread(); 30 | int actualThreadCount = expectedThreadCount; 31 | int expectedUserCount = cliArgs.getUserCount(); 32 | if (expectedUserCount <= cliArgs.getBatchSize()) { 33 | actualThreadCount = 2; 34 | } else if (expectedUserCount <= expectedThreadCount * cliArgs.getBatchSize()) { 35 | // 比如:总共只需要生成20000个用户,现在有10条线程,每个线程一次性生成10000条数据的情况 36 | actualThreadCount = expectedThreadCount / cliArgs.getBatchSize() + 1; 37 | } 38 | if (actualThreadCount <= 1) { 39 | actualThreadCount = 2; 40 | } 41 | if (actualThreadCount != expectedThreadCount) { 42 | logger.info("生成" + expectedUserCount + "个用户,不需要" + expectedThreadCount + "条线程,重新调整线程数为" + actualThreadCount); 43 | } 44 | cliArgs.setThread(actualThreadCount); 45 | } 46 | 47 | /** 48 | * 调节线程数. 49 | */ 50 | public static void regulateMaxItemPerUser(CliArgs cliArgs) { 51 | int expected = cliArgs.getMaxItemPerUser(); 52 | int actual = expected; 53 | if (expected >= cliArgs.getMaxItemPerUserExceed()) { 54 | actual = cliArgs.getMaxItemPerUserExceed(); 55 | logger.info("每个用户的item数量为" + expected + ",太大,重新调整为" + actual); 56 | } 57 | cliArgs.setMaxItemPerUser(actual); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/config/DatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.config; 2 | 3 | import com.wuda.tester.mysql.cli.CliArgs; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.tomcat.jdbc.pool.PoolConfiguration; 6 | import org.apache.tomcat.jdbc.pool.PoolProperties; 7 | import org.jooq.DSLContext; 8 | import org.jooq.SQLDialect; 9 | import org.jooq.impl.DSL; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | import javax.sql.DataSource; 14 | 15 | /** 16 | * 数据库相关配置. 17 | * 18 | * @author wuda 19 | */ 20 | @Configuration 21 | public class DatabaseConfiguration { 22 | 23 | /** 24 | * 获取{@link DataSource} 25 | * 26 | * @return {@link DataSource} 27 | */ 28 | @Bean 29 | public DataSource getDataSource() { 30 | PoolConfiguration poolProperties = new PoolProperties(); 31 | poolProperties.setDriverClassName("com.mysql.cj.jdbc.Driver"); 32 | return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties); 33 | } 34 | 35 | /** 36 | * 数据库连接的url,username,password等可以从命令行传入, 37 | * 使用命令行传入的数据库连接参数. 38 | * 39 | * @param dataSource datasource 40 | * @param cliArgs 命令行参数 41 | */ 42 | public static void applyArgs(DataSource dataSource, CliArgs cliArgs) { 43 | org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource; 44 | tomcatDataSource.setUrl(cliArgs.getMysqlUrl()); 45 | if (StringUtils.isNoneBlank(cliArgs.getMysqlUsername())) { 46 | tomcatDataSource.setUsername(cliArgs.getMysqlUsername()); 47 | } 48 | if (StringUtils.isNoneBlank(cliArgs.getMysqlPassword())) { 49 | tomcatDataSource.setPassword(cliArgs.getMysqlPassword()); 50 | } 51 | tomcatDataSource.setMaxActive(cliArgs.getMysqlMaxConnection()); 52 | } 53 | 54 | /** 55 | * jooq执行sql语句的context 56 | * 57 | * @return context 58 | */ 59 | @Bean 60 | public DSLContext getDSLContext() { 61 | return DSL.using(getDataSource(), SQLDialect.MYSQL); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 下载可执行JAR包,生成数据 2 | 3 | ``` 4 | 1. 安装Java JDK, Java版本 >= 1.8 5 | ``` 6 | 7 | ``` 8 | 2. 下载可执行jar文件 9 | ``` 10 | - [jar下载](https://github.com/wuda0112/mysql-tester/releases/) 11 | 12 | ``` 13 | 3. 创建数据库表,SQL文件和【相应版本的JAR在同一个地方下载】 14 | ``` 15 | - [mysql_tester.sql 脚步文件](https://github.com/wuda0112/mysql-tester/releases/) 16 | 17 | ``` 18 | 4. 输入命令,启动。默认连接到本地mysql,即: localhost:3306,最简单的就是 19 | 20 | java -jar mysql-tester-${VERSION}.jar --mysql-username=用户名 --mysql-password=密码 21 | ``` 22 | ``` 23 | 5. 每隔一分钟会输出每个表生成的数据量,比如以下输出,表示STORE表生成了24500条记录 24 | ``` 25 | ``` 26 | STORE=24500 27 | USER=24500 28 | ITEM_GENERAL=123145 29 | USER_PHONE=24500 30 | ITEM_DESCRIPTION=123145 31 | ITEM=123145 32 | ``` 33 | 34 | # clone项目 35 | ```aidl 36 | 1. 启动类: com.wuda.tester.mysql.Bootstrap 37 | 2. 启动之前必须配置 --mysql-username 和 --mysql-password 两个args,默认连接到本地mysql数据库,比如对于IDEA开发工具, 38 | 输入命令行参数的位置是:Run -> Edit Configuritions -> Configration -> Program arguments ,在输入框中输入 39 | --mysql-username=your username --mysql-password=your password 即可 40 | ``` 41 | 42 | # 简介 43 | 生成的数据规模是可配置的,比如指定生成100万用户,5000万商品;并且**数据之间有关联关系**,因此可以测试sql join等语句。 44 | 很多工具要么生成的数据是单表,即数据之间没有关联关系,要么数据量较小,对于很多测试看不到效果,本项目的目的就是既生成有关联关系的数据,又可以自定义数据规模! 45 | 46 | # 数据库表 47 | 不同的版本,所使用的表可能不一样,因为一直在增加更多的表。越高的版本,所使用的表的数量越多,已经发布的版本所使用的表,请查看对应版本的SQL文件 48 | 49 | ## 最新发布版本所使用的表,如下 50 | 数据库表选自于我的另外一个中台项目[**foundation**](https://github.com/wuda0112/foundation) 51 | ### foundation_user 52 | - - user,用户表 53 | - - individual_user_general,个人用户基本信息 54 | - - user_account,用户账号信息,适用各种类型的用户 55 | - - user_email,用户的email 56 | - - user_phone,用户的电话 57 | ### foundation_store 58 | - - store,店铺表 59 | - - store_general,店铺基本信息 60 | - - store_user_relationship,如果把用户ID字段放在store表中,表明店铺属于某个用户,但是如果有多个用户可以管理这个店铺呢?有种做法是一个用户作为另一个用户的子账号;也可以建立用户与店铺的关联关系,这样感觉更符合逻辑。把用户IID放在store表,可以很明确的表明店铺的owner,如果是用关联关系表的话,就需要明确的标明哪个用户是owner,哪些用户只是管理这个店铺的。 61 | ### foundation_item 62 | - - item,物品(商品)表 63 | - - item_general,物品基本信息 64 | - - item_description,物品描述信息 65 | - - item_variation,物品规格 66 | ### foundation_commons 67 | - - phone,电话表 68 | - - email,邮箱表 69 | - - property_key,属性的key 70 | - - property_value,属性的值 71 | 72 | # 数据量配置 73 | 查看 --user-count 和 --max-item-per-user 两个选项的说明 74 | 75 | # 支持的参数(必须放在mysql-tester-${VERSION}.jar后面) 76 | 77 | ``` 78 | --max-item-per-user 79 | 每个用户最多有多少商品数;在生成数据时,随机为每个用户生成商品,数量取值范围是 80 | [0,MAX](default=10).比如默认生成10000个用户,每个用户 81 | 最多10个商品,那么大致就可以知道生成的数据规模 82 | --mysql-max-connection mysql最大连接数(default=25),不是越大越好 83 | --mysql-password mysql password 84 | --mysql-url 85 | mysql连接url(default=jdbc:mysql://localho 86 | st:3306/?serverTimezone=UTC) 87 | --mysql-username mysql username 88 | --thread 生成数据的线程数(default=50),不是越大越好 89 | --user-count 90 | 用户表生成多少行记录,同时也是店铺表和仓库表的记录数,因为一个用户只拥有一个店 91 | 铺和一个仓库(default=10000),当生成的记录数达到该值时,数据生成 92 | 任务结束 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliArgs.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.cli; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * 命令行接口输入的参数生成的实体.方便程序后续调用. 11 | * 12 | * @author wuda 13 | */ 14 | @Data 15 | @ToString 16 | public class CliArgs { 17 | 18 | /** 19 | * logger. 20 | */ 21 | private Logger logger = LoggerFactory.getLogger(CliArgs.class); 22 | 23 | final static String HELP = "help"; 24 | final static String HELP_DESC = "print this help message"; 25 | final static String HELP_SHORT = "h"; 26 | 27 | final static String THREAD = "thread"; 28 | final static int THREAD_DEFAULT = 50; 29 | final static String THREAD_DESC = "生成数据的线程数(default=" + THREAD_DEFAULT + ")"; 30 | private int thread = THREAD_DEFAULT; 31 | 32 | final static String MYSQL_URL = "mysql-url"; 33 | final static String MYSQL_URL_DEFAULT = "jdbc:mysql://localhost:3306/?serverTimezone=UTC"; 34 | final static String MYSQL_URL_DESC = "mysql连接url(default=" + MYSQL_URL_DEFAULT + ")"; 35 | private String mysqlUrl = MYSQL_URL_DEFAULT; 36 | 37 | final static String MYSQL_USERNAME = "mysql-username"; 38 | final static String MYSQL_USERNAME_DESC = "mysql username"; 39 | private String mysqlUsername; 40 | 41 | final static String MYSQL_PASSWORD = "mysql-password"; 42 | final static String MYSQL_PASSWORD_DESC = "mysql password"; 43 | private String mysqlPassword; 44 | 45 | final static String MYSQL_MAX_CONNECTION = "mysql-max-connection"; 46 | final static int MYSQL_MAX_CONNECTION_DEFAULT = THREAD_DEFAULT / 2; 47 | final static String MYSQL_MAX_CONNECTION_DESC = "mysql最大连接数(default=" + MYSQL_MAX_CONNECTION_DEFAULT + ")"; 48 | private int mysqlMaxConnection = MYSQL_MAX_CONNECTION_DEFAULT; 49 | 50 | final static String USER_COUNT = "user-count"; 51 | final static int USER_COUNT_DEFAULT = 10000; 52 | final static String USER_COUNT_DESC = "用户表生成多少行记录,同时也是店铺表和仓库表的记录数,因为一个用户只拥有一个店铺和一个仓库" + 53 | "(default=" + USER_COUNT_DEFAULT + ")" + 54 | ",当生成的记录数达到该值时,数据生成任务结束"; 55 | private int userCount = USER_COUNT_DEFAULT; 56 | 57 | final static String MAX_ITEM_PER_USER = "max-item-per-user"; 58 | final static int MAX_ITEM_PER_USER_DEFAULT = 10; 59 | final static String MAX_ITEM_PER_USER_DESC = "每个用户最多有多少商品数;在生成数据时,随机为每个用户生成商品,数量取值范围是[0,MAX]" + 60 | "(default=" + MAX_ITEM_PER_USER_DEFAULT + ")." + 61 | "比如默认生成" + USER_COUNT_DEFAULT + "个用户,每个用户最多" + MAX_ITEM_PER_USER_DEFAULT + "个商品," + 62 | "那么大致就可以知道生成的数据规模"; 63 | private int maxItemPerUser = MAX_ITEM_PER_USER_DEFAULT; 64 | private int maxItemPerUserExceed = 20; 65 | 66 | final static String BATCH_SIZE = "batch-size"; 67 | public final static int BATCH_SIZE_DEFAULT = 100; 68 | final static String BATCH_SIZE_DESC = "每个线程一次性批量生成的用户数.为了提高生成数据的性能,使用\n" + 69 | " \n" + 70 | " insert into table\n" + 71 | " (column)\n" + 72 | " values(a),\n" + 73 | " ` (b),\n" + 74 | " (c);\n" + 75 | " 这样的语法,这个值就表示value的数量." + 76 | "(default=" + BATCH_SIZE_DEFAULT + ")"; 77 | private int batchSize = BATCH_SIZE_DEFAULT; 78 | 79 | /** 80 | * 自我校验. 81 | */ 82 | public void validate() { 83 | if (StringUtils.isBlank(getMysqlUsername())) { 84 | logger.warn("没有指定数据库用户名,在命令行参数中,使用--" + MYSQL_USERNAME + "设置"); 85 | System.exit(0); 86 | } 87 | if (StringUtils.isBlank(getMysqlPassword())) { 88 | logger.warn("没有指定数据库密码,在命令行参数中,使用--" + MYSQL_PASSWORD + "设置"); 89 | System.exit(0); 90 | } 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /mysql-data-generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.github.wuda0112 7 | mysql-tester 8 | 1.0.5-SNAPSHOT 9 | 10 | 11 | mysql-data-generator 12 | mysql-data-generator 13 | http://maven.apache.org 14 | 15 | 16 | 3.13.2 17 | 1.0.2 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-configuration-processor 28 | 29 | 30 | org.springframework 31 | spring-tx 32 | 33 | 34 | org.springframework 35 | spring-jdbc 36 | 37 | 38 | org.apache.tomcat 39 | tomcat-jdbc 40 | 41 | 42 | javax.persistence 43 | javax.persistence-api 44 | 45 | 46 | commons-cli 47 | commons-cli 48 | 49 | 50 | 51 | mysql 52 | mysql-connector-java 53 | 54 | 55 | 56 | io.github.wuda0112 57 | mysql-tester-commons 58 | ${project.version} 59 | 60 | 61 | 62 | 63 | io.github.wuda0112 64 | foundation-lang 65 | ${foundation.version} 66 | 67 | 68 | io.github.wuda0112 69 | foundation-commons-impl 70 | ${foundation.version} 71 | 72 | 73 | io.github.wuda0112 74 | foundation-user-impl 75 | ${foundation.version} 76 | 77 | 78 | io.github.wuda0112 79 | foundation-store-impl 80 | ${foundation.version} 81 | 82 | 83 | io.github.wuda0112 84 | foundation-item-impl 85 | ${foundation.version} 86 | 87 | 88 | 89 | 90 | 91 | mysql-tester-${version} 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-maven-plugin 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/utils/StringUtilsExt.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.commons.utils; 2 | 3 | /** 4 | * string util. 5 | * 6 | * @author wuda 7 | */ 8 | public class StringUtilsExt { 9 | 10 | /** 11 | * 空字符串. 12 | */ 13 | public static String EMPTY_STRING = ""; 14 | 15 | /** 16 | * 大小写枚举. 17 | */ 18 | public static enum CaseEnum { 19 | upper, lower; 20 | } 21 | 22 | /** 23 | * 将第一个字符大写. 24 | * 25 | * @param str 字符串 26 | * @return 新的字符串 27 | */ 28 | public static String firstCharToUpperCase(String str) { 29 | return changeCaseAt(str, 0, CaseEnum.upper); 30 | } 31 | 32 | /** 33 | * 将第一个字符小写. 34 | * 35 | * @param str 字符串 36 | * @return 新的字符串 37 | */ 38 | public static String firstCharToLowerCase(String str) { 39 | return changeCaseAt(str, 0, CaseEnum.lower); 40 | } 41 | 42 | /** 43 | * 改变指定位置字符的大小写. 44 | * 45 | * @param str string 46 | * @param index 位置 47 | * @param lowerOrUpper lower or upper 48 | * @return new string 49 | */ 50 | public static String changeCaseAt(String str, int index, CaseEnum lowerOrUpper) { 51 | if (str == null || str.isEmpty()) { 52 | return str; 53 | } 54 | if (index >= str.length()) { 55 | return str; 56 | } 57 | char oldChar = str.charAt(index); 58 | char newChar = 0x00; 59 | switch (lowerOrUpper) { 60 | case upper: 61 | newChar = Character.toUpperCase(oldChar); 62 | break; 63 | case lower: 64 | newChar = Character.toLowerCase(oldChar); 65 | break; 66 | } 67 | if (newChar == oldChar) { 68 | return str; 69 | } 70 | char[] copy = str.toCharArray(); 71 | copy[index] = newChar; 72 | return new String(copy); 73 | } 74 | 75 | /** 76 | * 为字符串添加给定的前缀. 77 | * 78 | * @param str 原始字符串 79 | * @param prefix 前缀 80 | * @return 前缀与原始字符串组成的新字符串 81 | */ 82 | public static String addPrefix(String str, String prefix) { 83 | if (prefix == null || prefix.isEmpty()) { 84 | return str; 85 | } 86 | if (str == null || str.isEmpty()) { 87 | return prefix; 88 | } 89 | char[] chars = new char[str.length() + prefix.length()]; 90 | for (int i = 0; i < prefix.length(); i++) { 91 | chars[i] = prefix.charAt(i); 92 | } 93 | int k = prefix.length(); 94 | for (int j = 0; j < str.length(); j++) { 95 | chars[k++] = str.charAt(j); 96 | } 97 | return new String(chars); 98 | } 99 | 100 | /** 101 | * 为字符串添加给定的后缀. 102 | * 103 | * @param str 原始字符串 104 | * @param suffix 后缀 105 | * @return 原始字符串与后缀组成的新字符串 106 | */ 107 | public static String addSuffix(String str, String suffix) { 108 | return addPrefix(suffix, str); 109 | } 110 | 111 | /** 112 | * 去除掉最后一个char. 113 | * 114 | * @param charSequence char sequence 115 | * @return 去除最后一个字符后的字符串 116 | */ 117 | public static String removeLastChar(CharSequence charSequence) { 118 | if (charSequence == null) { 119 | return null; 120 | } else if (charSequence.length() <= 1) { 121 | return EMPTY_STRING; 122 | } else { 123 | return charSequence.subSequence(0, lastCharIndex(charSequence)).toString(); 124 | } 125 | } 126 | 127 | /** 128 | * 最后一个字符的下标. 129 | * 130 | * @param charSequence char sequence 131 | * @return 最后一个字符的下标 132 | */ 133 | public static int lastCharIndex(CharSequence charSequence) { 134 | return charSequence.length() - 1; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/utils/RandomUtilsExt.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.commons.utils; 2 | 3 | 4 | import org.apache.commons.lang3.RandomUtils; 5 | 6 | /** 7 | * 对Apache RandomUtils的扩展.补充一些其他的随机方法. 8 | * 9 | * @author wuda 10 | */ 11 | public class RandomUtilsExt { 12 | 13 | /** 14 | * 在给定的数组中随机选择一个数字. 15 | * 16 | * @param candidates 候选数字,至少要有一个元素,否则抛出异常 17 | * @return 数组中的任意一个数字 18 | */ 19 | public static int nextInt(int candidates[]) { 20 | if (candidates == null || candidates.length <= 0) { 21 | throw new IllegalArgumentException("候选数组中至少要有一个元素!"); 22 | } 23 | int index = RandomUtils.nextInt(0, candidates.length); 24 | return candidates[index]; 25 | } 26 | 27 | /** 28 | * 随机字符串. 29 | * 30 | * @param length 字符串长度,如果小于1,则返回空字符串(不是null). 31 | * @return 字符串 32 | */ 33 | public static String randomString(int length) { 34 | return randomString(length, Character.MIN_CODE_POINT, Character.MAX_CODE_POINT); 35 | } 36 | 37 | public static String enRandomString(int length) { 38 | int[] enCodePoints = new int[]{65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90}; 39 | StringBuilder stringBuilder = new StringBuilder(length); 40 | for (int k = 0; k < length; k++) { 41 | char ch = (char) nextInt(enCodePoints); 42 | stringBuilder.append(ch); 43 | } 44 | return stringBuilder.toString(); 45 | } 46 | 47 | /** 48 | * 生成随机字符串.字符从给定的code point范围内随机获取. 49 | * 50 | * @param length 长度 51 | * @param codePointStartInclusive unicode code point范围的起始值,包含 52 | * @param codePointEndExclusive unicode code point范围的结束值,不包含 53 | * @return 随机字符串 54 | */ 55 | public static String randomString(int length, int codePointStartInclusive, int codePointEndExclusive) { 56 | if (length <= 0) { 57 | return StringUtilsExt.EMPTY_STRING; 58 | } 59 | StringBuilder builder = new StringBuilder(length); 60 | for (int i = 0; i < length; i++) { 61 | builder.append(randomChar(codePointStartInclusive, codePointEndExclusive)); 62 | } 63 | return builder.toString(); 64 | } 65 | 66 | /** 67 | * 随机生成字符串.至少包含英文字母,汉字,数字中的一种. 68 | * 69 | * @param length 字符串长度 70 | * @param hasLetter 是否包含英文字母 71 | * @param hasHan 是否包含中文汉字 72 | * @param hasNumber 是否包含数字 73 | * @return 随机字符串 74 | */ 75 | public static String randomString(int length, boolean hasLetter, boolean hasHan, boolean hasNumber) { 76 | if (!hasLetter && !hasHan && !hasNumber) { 77 | throw new IllegalArgumentException("至少包含一种字符!"); 78 | } 79 | if (length <= 0) { 80 | return StringUtilsExt.EMPTY_STRING; 81 | } 82 | return null; 83 | } 84 | 85 | /** 86 | * 在给定的范围内生成随机char. 87 | * 88 | * @param codePointStartInclusive unicode code point 结束值,不包含 89 | * @param codePointEndExclusive unicode code point 起始值,包含 90 | * @return char 91 | */ 92 | public static char randomChar(int codePointStartInclusive, int codePointEndExclusive) { 93 | return (char) RandomUtils.nextInt(codePointStartInclusive, codePointEndExclusive); 94 | } 95 | 96 | /** 97 | * 随机给出一个char. 98 | * 99 | * @return char 100 | */ 101 | public static char randomChar() { 102 | return randomChar(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT); 103 | } 104 | 105 | /** 106 | * 常用邮箱. 107 | */ 108 | static String[] emailSuffix = new String[]{"@163.com", "@126.com", "@gmail.com", "@sina.com", "@sina.cn", "@qq.com"}; 109 | 110 | /** 111 | * 随机给出一个邮箱后缀,即@符号以后(包含)的部分.比如: @163.com 112 | * 113 | * @return 随机的邮箱后缀 114 | */ 115 | public static String randomEmailSuffix() { 116 | int index = RandomUtils.nextInt(0, emailSuffix.length); 117 | return emailSuffix[index]; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KeyGeneratorSnowflake.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.commons.keygen; 2 | 3 | /** 4 | * Twitter Snowflake算法实现.用于分布式环境下生成不重复ID. 5 | * 6 | * @author wuda 7 | * @version 1.0 8 | */ 9 | public class KeyGeneratorSnowflake { 10 | 11 | private int time_bit_count = 41; // bit数量,用于表示时间 12 | private int node_bit_count = 10; // bit数量,用于表示集群中节点 13 | private int sequence_bit_count = 12; // bit数量,用于表示序列号 14 | private long epoch = 1523289600000L;// 此算法编码完成的时间 15 | 16 | private int time_shift = sequence_bit_count + node_bit_count; 17 | private int node_shift = sequence_bit_count; 18 | 19 | private long max_timestamp = epoch + ((1L << time_bit_count) - 1); // 最大时间戳 20 | private int max_node_no = (1 << node_bit_count) - 1; // 最大节点编号 21 | private int max_sequence = (1 << sequence_bit_count) - 1; // 最大序列号 22 | 23 | private long timestamp; 24 | private long nodeNo;// 代表集群中节点的编号 25 | private long sequence = 0; 26 | 27 | /** 28 | * 为给定的节点构造一个key生成器. 29 | * 30 | * @param nodeNo 代表集群中节点的编号,从0开始 31 | */ 32 | public KeyGeneratorSnowflake(int nodeNo) { 33 | checkNodeNo(nodeNo); 34 | this.nodeNo = nodeNo; 35 | try { 36 | this.timestamp = getTimestamp(); 37 | } catch (KeyGenExceedMaxValueException | KenGenTimeBackwardsException e) { 38 | throw new IllegalStateException(e); 39 | } 40 | } 41 | 42 | /** 43 | * 检查给定的节点编号是否在指定范围内. 44 | * 45 | * @param nodeNo 节点编号 46 | */ 47 | private void checkNodeNo(int nodeNo) { 48 | if (nodeNo < 0 || nodeNo > max_node_no) { 49 | throw new IllegalArgumentException("节点编号允许的范围是:[0," + max_node_no + "],当前节点编号是:" + nodeNo); 50 | } 51 | } 52 | 53 | /** 54 | * 返回下一个key. 55 | * 56 | * @return 下一个key 57 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常 58 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了 59 | */ 60 | public synchronized long next() throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException { 61 | sequence++; 62 | if (sequence > max_sequence) { 63 | timestamp = getTimestamp(); 64 | sequence = 0; 65 | } 66 | return ((timestamp - epoch) << time_shift) | (nodeNo << node_shift) | sequence; 67 | } 68 | 69 | /** 70 | * 返回指定数量的key. 71 | * 72 | * @param size 一次性获取多个key 73 | * @return keys 74 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常 75 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了 76 | */ 77 | public synchronized long[] next(int size) throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException { 78 | if (size <= 0) { 79 | throw new IllegalArgumentException("size必须大于0"); 80 | } 81 | if (size == 1) { 82 | return new long[]{next()}; 83 | } 84 | long[] keys = new long[size]; 85 | for (int i = 0; i < size; i++) { 86 | keys[i] = next(); 87 | } 88 | return keys; 89 | } 90 | 91 | /** 92 | * 获取时间戳. 93 | * 94 | * @return 时间戳 95 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常 96 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了 97 | */ 98 | private long getTimestamp() throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException { 99 | long currentTimeMillis = System.currentTimeMillis(); 100 | if (currentTimeMillis < epoch) { 101 | throw new KenGenTimeBackwardsException("时间被向后调了?当前机器的时间戳是:" + currentTimeMillis + 102 | ",纪元时间戳是:" + epoch); 103 | } 104 | while (currentTimeMillis == timestamp) { 105 | currentTimeMillis = System.currentTimeMillis(); 106 | } 107 | if (currentTimeMillis < timestamp) { 108 | throw new KenGenTimeBackwardsException("时间被向后调了?当前机器的时间戳是:" + currentTimeMillis + 109 | ",最近一次记录的时间戳是:" + timestamp); 110 | } 111 | if (currentTimeMillis > max_timestamp) { 112 | throw new KeyGenExceedMaxValueException("omg!你的系统已经运行了差不多69年,恭喜!然而此ID生成器已经无法再继续提供服务了,bye!"); 113 | } 114 | return currentTimeMillis; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/statistic/DataGenerateStat.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.statistic; 2 | 3 | import com.wuda.tester.mysql.TableName; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * 数据生成过程中的统计信息.该类不是线程安全的,所以统计信息不是精确的, 但是也不会太离谱,尽可能会减小误差. 12 | * 13 | * @author wuda 14 | */ 15 | public class DataGenerateStat { 16 | 17 | /** 18 | * 数据生成任务执行的总次数. 19 | */ 20 | private AtomicInteger totalTaskCount = new AtomicInteger(); 21 | /** 22 | * 数据生成任务成功的次数. 23 | */ 24 | private AtomicInteger successTaskCount = new AtomicInteger(); 25 | /** 26 | * 数据生成任务失败的次数. 27 | */ 28 | private AtomicInteger failureTaskCount = new AtomicInteger(); 29 | 30 | /** 31 | * 已经保存到数据库的数量.key-表名,value-数量 32 | */ 33 | private Map inserted_entity_count = new ConcurrentHashMap<>(); 34 | 35 | public DataGenerateStat() { 36 | TableName[] tableNames = TableName.values(); 37 | for (TableName tableName : tableNames) { 38 | inserted_entity_count.put(tableName, new AtomicInteger()); 39 | } 40 | } 41 | 42 | /** 43 | * 获取统计的指标,以及该指标的数量. 44 | * 45 | * @return metric and count 46 | */ 47 | public Set> getTableCount() { 48 | return inserted_entity_count.entrySet(); 49 | } 50 | 51 | /** 52 | * 获取给定统计指标已经插入的数量. 53 | * 54 | * @param tableName 统计指标 55 | * @return 数量 56 | */ 57 | public int getInsertedCount(TableName tableName) { 58 | AtomicInteger counter = inserted_entity_count.get(tableName); 59 | if (counter != null) { 60 | return counter.get(); 61 | } 62 | return 0; 63 | } 64 | 65 | /** 66 | * 给定的统计指标数量加一,并且返回增加后的值. 67 | * 68 | * @param tableName 统计指标 69 | * @param count 增加的数量 70 | * @return 增加后的数量 71 | */ 72 | public int insertedIncrementAndGet(TableName tableName, int count) { 73 | return inserted_entity_count.get(tableName).addAndGet(count); 74 | } 75 | 76 | /** 77 | * 增加并且获取{@link #totalTaskCount}. 78 | * 79 | * @return int 80 | */ 81 | public int incrementAndGetTotalTaskCount() { 82 | return totalTaskCount.incrementAndGet(); 83 | } 84 | 85 | /** 86 | * 获取{@link #totalTaskCount}. 87 | * 88 | * @return int 89 | */ 90 | public int getTotalTaskCount() { 91 | return totalTaskCount.get(); 92 | } 93 | 94 | /** 95 | * 增加并且获取{@link #successTaskCount}. 96 | * 97 | * @return int 98 | */ 99 | public int incrementAndGetSuccessTaskCount() { 100 | return successTaskCount.incrementAndGet(); 101 | } 102 | 103 | /** 104 | * 获取{@link #successTaskCount}. 105 | * 106 | * @return int 107 | */ 108 | public int getSuccessTaskCount() { 109 | return successTaskCount.get(); 110 | } 111 | 112 | /** 113 | * 增加并且获取{@link #failureTaskCount}. 114 | * 115 | * @return int 116 | */ 117 | public int incrementAndGetFailureTaskCount() { 118 | return failureTaskCount.incrementAndGet(); 119 | } 120 | 121 | /** 122 | * 获取{@link #failureTaskCount}. 123 | * 124 | * @return int 125 | */ 126 | public int getFailureTaskCount() { 127 | return failureTaskCount.get(); 128 | } 129 | 130 | /** 131 | * 获取失败率. 132 | * 133 | * @return 失败率 134 | */ 135 | public float getFailureRate() { 136 | int totalPlus1 = totalTaskCount.get() + 1; 137 | return (float) failureTaskCount.get() / totalPlus1; 138 | } 139 | 140 | public String toString() { 141 | StringBuilder builder = new StringBuilder("生成数据统计信息:"); 142 | builder.append("totalTaskCount =").append(totalTaskCount).append(",") 143 | .append("successTaskCount = ").append(successTaskCount).append(",") 144 | .append("failureTaskCount = " + failureTaskCount).append(".\n"); 145 | Set> entrySet = inserted_entity_count.entrySet(); 146 | for (Map.Entry entry : entrySet) { 147 | builder.append("\t").append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); 148 | } 149 | return builder.toString(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/FoundationBasedDataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.foundation.commons.EmailManager; 4 | import com.wuda.foundation.commons.PhoneManager; 5 | import com.wuda.foundation.commons.impl.EmailManagerImpl; 6 | import com.wuda.foundation.commons.impl.PhoneManagerImpl; 7 | import com.wuda.foundation.commons.impl.PropertyManagerImpl; 8 | import com.wuda.foundation.commons.property.PropertyManager; 9 | import com.wuda.foundation.item.ItemManager; 10 | import com.wuda.foundation.item.impl.ItemManagerImpl; 11 | import com.wuda.foundation.store.StoreManager; 12 | import com.wuda.foundation.store.impl.StoreManagerImpl; 13 | import com.wuda.foundation.user.UserManager; 14 | import com.wuda.foundation.user.impl.UserManagerImpl; 15 | import com.wuda.tester.mysql.TableName; 16 | import com.wuda.tester.mysql.cli.CliArgs; 17 | 18 | import javax.sql.DataSource; 19 | 20 | /** 21 | * 基于foundation项目生成数据. 22 | * 23 | * @author wuda 24 | */ 25 | public class FoundationBasedDataGenerator extends DataGenerator { 26 | 27 | private long opUserId = 1L; 28 | 29 | /** 30 | * 构造实例. 31 | * 32 | * @param cliArgs 命令行参数 33 | * @param dataSource datasource 34 | */ 35 | public FoundationBasedDataGenerator(CliArgs cliArgs, DataSource dataSource) { 36 | super(cliArgs, dataSource); 37 | } 38 | 39 | @Override 40 | public FoundationBasedDataSet prepareDataSet(CliArgs cliArgs) { 41 | FoundationBasedDataSet dataSet = new FoundationBasedDataSet(); 42 | dataSet.prepare(cliArgs); 43 | return dataSet; 44 | } 45 | 46 | @Override 47 | public void insert(FoundationBasedDataSet dataSet) { 48 | generateUserData(dataSet); 49 | generateStore(dataSet); 50 | generateItem(dataSet); 51 | generateProperty(dataSet); 52 | } 53 | 54 | public void generateUserData(FoundationBasedDataSet dataSet) { 55 | UserManager userManager = getUserManager(); 56 | EmailManager emailManager = getEmailManager(); 57 | PhoneManager phoneManager = getPhoneManager(); 58 | 59 | userManager.directBatchInsertUser(dataSet.getUsers(), opUserId); 60 | userManager.directBatchInsertUserAccount(dataSet.getUserAccounts(), opUserId); 61 | emailManager.directBatchInsertEmail(dataSet.getEmails(), opUserId); 62 | userManager.directBatchBindUserEmail(dataSet.getBindUserEmails(), opUserId); 63 | phoneManager.directBatchInsertPhone(dataSet.getPhones(), opUserId); 64 | userManager.directBatchBindUserPhone(dataSet.getBindUserPhones(), opUserId); 65 | 66 | dataGenerateStat.insertedIncrementAndGet(TableName.USER, dataSet.getUsers().size()); 67 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_ACCOUNT, dataSet.getUserAccounts().size()); 68 | dataGenerateStat.insertedIncrementAndGet(TableName.EMAIL, dataSet.getEmails().size()); 69 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_EMAIL, dataSet.getBindUserEmails().size()); 70 | dataGenerateStat.insertedIncrementAndGet(TableName.PHONE, dataSet.getPhones().size()); 71 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_PHONE, dataSet.getBindUserPhones().size()); 72 | } 73 | 74 | public void generateStore(FoundationBasedDataSet dataSet) { 75 | StoreManager storeManager = getStoreManager(); 76 | 77 | storeManager.directBatchInsertStore(dataSet.getStores(), opUserId); 78 | storeManager.directBatchBindStoreUser(dataSet.getBindStoreUsers(), opUserId); 79 | storeManager.directBatchInsertStoreGeneral(dataSet.getStoreGenerals(), opUserId); 80 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE, dataSet.getStores().size()); 81 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE_USER_RELATIONSHIP, dataSet.getBindStoreUsers().size()); 82 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE_GENERAL, dataSet.getStoreGenerals().size()); 83 | } 84 | 85 | public void generateItem(FoundationBasedDataSet dataSet) { 86 | ItemManager itemManager = getItemManager(); 87 | 88 | itemManager.directBatchInsertItem(dataSet.getItems(), opUserId); 89 | itemManager.directBatchInsertItemGeneral(dataSet.getItemGenerals(), opUserId); 90 | itemManager.directBatchInsertItemVariation(dataSet.getItemVariations(), opUserId); 91 | itemManager.directBatchInsertItemDescription(dataSet.getItemDescriptions(), opUserId); 92 | 93 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM, dataSet.getItems().size()); 94 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_GENERAL, dataSet.getItemGenerals().size()); 95 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_VARIATION, dataSet.getItemVariations().size()); 96 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_DESCRIPTION, dataSet.getItemDescriptions().size()); 97 | } 98 | 99 | public void generateProperty(FoundationBasedDataSet dataSet) { 100 | PropertyManager propertyManager = getPropertyManager(); 101 | 102 | propertyManager.directBatchInsertPropertyKey(dataSet.getPropertyKeys(), opUserId); 103 | propertyManager.directBatchInsertPropertyValue(dataSet.getPropertyValues(), opUserId); 104 | 105 | dataGenerateStat.insertedIncrementAndGet(TableName.PROPERTY_KEY, dataSet.getPropertyKeys().size()); 106 | dataGenerateStat.insertedIncrementAndGet(TableName.PROPERTY_VALUE, dataSet.getPropertyValues().size()); 107 | } 108 | 109 | private UserManager getUserManager() { 110 | UserManagerImpl userManager = new UserManagerImpl(); 111 | userManager.setDataSource(dataSource); 112 | return userManager; 113 | } 114 | 115 | private EmailManager getEmailManager() { 116 | EmailManagerImpl emailManager = new EmailManagerImpl(); 117 | emailManager.setDataSource(dataSource); 118 | return emailManager; 119 | } 120 | 121 | private PhoneManager getPhoneManager() { 122 | PhoneManagerImpl phoneManager = new PhoneManagerImpl(); 123 | phoneManager.setDataSource(dataSource); 124 | return phoneManager; 125 | } 126 | 127 | private StoreManager getStoreManager() { 128 | StoreManagerImpl storeManager = new StoreManagerImpl(); 129 | storeManager.setDataSource(dataSource); 130 | return storeManager; 131 | } 132 | 133 | private ItemManager getItemManager() { 134 | ItemManagerImpl storeManager = new ItemManagerImpl(); 135 | storeManager.setDataSource(dataSource); 136 | return storeManager; 137 | } 138 | 139 | private PropertyManager getPropertyManager() { 140 | PropertyManagerImpl propertyManager = new PropertyManagerImpl(); 141 | propertyManager.setDataSource(dataSource); 142 | return propertyManager; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.tester.mysql.cli.CliArgs; 4 | import com.wuda.tester.mysql.statistic.DataGenerateStat; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.sql.DataSource; 9 | import java.util.Date; 10 | import java.util.concurrent.BlockingQueue; 11 | import java.util.concurrent.LinkedBlockingQueue; 12 | import java.util.concurrent.ThreadFactory; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | /** 16 | * 数据生成器,随机生成数据并且插入数据库.在生成数据的过程中,使用{@link DataGenerateStat}统计, 17 | * 然后{@link DataGenerateStopPolicy}根据这些统计信息就可以判断是否停止生成数据, 18 | * 确定可以停止后,启动的线程将会停止并退出. 19 | * 20 | * @param 具体的{@link DataSet}的类型 21 | * @author wuda 22 | */ 23 | public abstract class DataGenerator { 24 | 25 | /** 26 | * logger. 27 | */ 28 | private static Logger logger = LoggerFactory.getLogger(DataGenerator.class); 29 | /** 30 | * thread factory. 31 | */ 32 | private static DefaultThreadFactory threadFactory = new DefaultThreadFactory(); 33 | /** 34 | * 数据生成过程中的统计信息. 35 | */ 36 | protected DataGenerateStat dataGenerateStat = new DataGenerateStat(); 37 | /** 38 | * 停止数据生成策略. 39 | */ 40 | private DataGenerateStopPolicy stopPolicy; 41 | 42 | private CliArgs cliArgs; 43 | private BlockingQueue queue; 44 | protected DataSource dataSource; 45 | 46 | /** 47 | * 系统启动时间. 48 | */ 49 | private Date startupTime; 50 | 51 | /** 52 | * 构造实例. 53 | * 54 | * @param cliArgs 命令行参数 55 | * @param dataSource datasource 56 | */ 57 | public DataGenerator(CliArgs cliArgs, DataSource dataSource) { 58 | this.cliArgs = cliArgs; 59 | this.stopPolicy = new DefaultDataGeneratorStopPolicy(cliArgs); 60 | this.queue = new LinkedBlockingQueue<>(cliArgs.getThread()); 61 | this.dataSource = dataSource; 62 | } 63 | 64 | /** 65 | * 正在运行的Consumer的数量. 66 | */ 67 | private AtomicInteger runningConsumerCount = new AtomicInteger(0); 68 | /** 69 | * 正在运行的Producer的数量. 70 | */ 71 | private AtomicInteger runningProducerCount = new AtomicInteger(0); 72 | 73 | /** 74 | * 准备数据. 75 | * 76 | * @param cliArgs 包含了数据量参数,这样才知道每次生成多少数据量比较合适 77 | * @return data set 78 | */ 79 | public abstract T prepareDataSet(CliArgs cliArgs); 80 | 81 | /** 82 | * 将准备好的数据插入到数据库中. 83 | * 84 | * @param dataSet 准备好的数据 85 | */ 86 | public abstract void insert(T dataSet); 87 | 88 | 89 | /** 90 | * 根据总的线程数,分配Producer占用的线程数. 91 | * 92 | * @param totalThreadSize 总的线程数 93 | * @return Producer的线程数 94 | */ 95 | public static int allocateProducerThreadSize(int totalThreadSize) { 96 | float producerThreadSize = totalThreadSize * 0.01F; 97 | return (int) Math.ceil(producerThreadSize); 98 | } 99 | 100 | /** 101 | * 线程sleep.当catch到{@link InterruptedException}异常,使用{@link RuntimeException}的方式将异常抛出. 102 | * 103 | * @param millis millis 104 | */ 105 | public void threadSleepSilence(long millis) { 106 | try { 107 | Thread.sleep(millis); 108 | } catch (InterruptedException e) { 109 | throw new RuntimeException("线程sleep时收到了InterruptedException", e); 110 | } 111 | } 112 | 113 | /** 114 | * 启动生产者,消费者,监控线程. 115 | */ 116 | public void startup() { 117 | startupTime = new Date(); 118 | int producerSize = allocateProducerThreadSize(cliArgs.getThread()); 119 | int consumerSize = cliArgs.getThread() - producerSize; 120 | for (int i = 0; i < producerSize; i++) { 121 | threadFactory.newThread(new Producer("Producer-" + i)).start(); 122 | } 123 | threadSleepSilence(30 * 1000); // 让producer先产生下数据 124 | for (int i = 0; i < consumerSize; i++) { 125 | threadFactory.newThread(new Consumer("Consumer-" + i)).start(); 126 | } 127 | MonitorThread monitorThread = new MonitorThread(); 128 | monitorThread.setDaemon(true); 129 | monitorThread.start(); 130 | 131 | while (runningConsumerCount.get() > 0 || runningProducerCount.get() > 0) { 132 | threadSleepSilence(60 * 1000); 133 | } 134 | } 135 | 136 | /** 137 | * the Producer. 138 | * 139 | * @author wuda 140 | */ 141 | public class Producer implements Runnable { 142 | 143 | /** 144 | * Producer的名称. 145 | */ 146 | private String name; 147 | 148 | /** 149 | * 构造指定名称的Producer. 150 | * 151 | * @param name 名称 152 | */ 153 | public Producer(String name) { 154 | this.name = name; 155 | } 156 | 157 | @Override 158 | public void run() { 159 | runningProducerCount.incrementAndGet(); 160 | while (!stopPolicy.stop(dataGenerateStat)) { 161 | T dataSet = prepareDataSet(cliArgs); 162 | try { 163 | queue.put(dataSet); 164 | } catch (InterruptedException e) { 165 | logger.warn("producer = {} Interrupted", name); 166 | } 167 | } 168 | runningProducerCount.decrementAndGet(); 169 | if (runningProducerCount.get() <= 0) { 170 | logger.info(dataGenerateStat.toString()); 171 | } 172 | logger.info("Producer ={} 退出,{}", name, stopPolicy.getStopMessage()); 173 | } 174 | } 175 | 176 | /** 177 | * the Consumer. 178 | * 179 | * @author wuda 180 | */ 181 | public class Consumer implements Runnable { 182 | 183 | /** 184 | * consumer的名称. 185 | */ 186 | private String name; 187 | 188 | /** 189 | * 构造指定名称的Consumer. 190 | * 191 | * @param name 名称 192 | */ 193 | public Consumer(String name) { 194 | this.name = name; 195 | } 196 | 197 | @Override 198 | public void run() { 199 | runningConsumerCount.incrementAndGet(); 200 | T dataSet = null; 201 | while ((dataSet = queue.poll()) != null || runningProducerCount.get() > 0) { 202 | if (dataSet != null) { 203 | try { 204 | dataGenerateStat.incrementAndGetTotalTaskCount(); 205 | insert(dataSet); 206 | dataGenerateStat.incrementAndGetSuccessTaskCount(); 207 | } catch (Exception e) { 208 | logger.error("Consumer = {},生成数据异常", name, e); 209 | dataGenerateStat.incrementAndGetFailureTaskCount(); 210 | } 211 | } else { 212 | logger.info("queue没有获取到元素,可以考虑加大Producer数量,Consumer count ={} ,Producer count = {}" 213 | , runningConsumerCount.get(), runningProducerCount.get()); 214 | } 215 | } 216 | runningConsumerCount.decrementAndGet(); 217 | logger.info("Consumer ={} 退出 ", name); 218 | } 219 | } 220 | 221 | /** 222 | * 用于监控生成数据的情况. 223 | * 224 | * @author wuda 225 | */ 226 | public class MonitorThread extends Thread { 227 | 228 | @Override 229 | public void run() { 230 | while (runningConsumerCount.get() > 0 || runningProducerCount.get() > 0) { 231 | logger.info("启动时间: {}", startupTime); 232 | logger.info("DataSet queue中暂存的DataSet数量是:{},Consumer Count = {},Producer Count = {}" 233 | , queue.size(), runningConsumerCount.get(), runningProducerCount.get()); 234 | logger.info(dataGenerateStat.toString()); 235 | threadSleepSilence(60 * 1000); 236 | } 237 | logger.info("monitor thread exit"); 238 | } 239 | 240 | } 241 | 242 | /** 243 | * The default thread factory 244 | */ 245 | static class DefaultThreadFactory implements ThreadFactory { 246 | private final ThreadGroup group; 247 | private final AtomicInteger threadNumber = new AtomicInteger(1); 248 | private final String namePrefix; 249 | 250 | DefaultThreadFactory() { 251 | SecurityManager s = System.getSecurityManager(); 252 | group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); 253 | namePrefix = "generator-"; 254 | } 255 | 256 | public Thread newThread(Runnable r) { 257 | Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); 258 | if (t.isDaemon()) 259 | t.setDaemon(false); 260 | if (t.getPriority() != Thread.NORM_PRIORITY) 261 | t.setPriority(Thread.NORM_PRIORITY); 262 | return t; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliOptionUtils.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.cli; 2 | 3 | import org.apache.commons.cli.*; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * 命令行工具类. 10 | * 11 | * @author wuda 12 | */ 13 | public class CliOptionUtils { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(CliOptionUtils.class); 16 | 17 | /** 18 | * 支持的选项. 19 | * 20 | * @return 选项 21 | */ 22 | public static Options getOptions() { 23 | // 定义一个可选选项集 24 | Options options = new Options(); 25 | options.addOption(Option.builder(CliArgs.HELP_SHORT) 26 | .longOpt(CliArgs.HELP) 27 | .argName(CliArgs.HELP) 28 | .desc(CliArgs.HELP_DESC) 29 | .build()); 30 | options.addOption(Option.builder() 31 | .longOpt(CliArgs.THREAD) 32 | .argName(CliArgs.THREAD) 33 | .hasArg() 34 | .desc(CliArgs.THREAD_DESC) 35 | .build()); 36 | options.addOption(Option.builder() 37 | .longOpt(CliArgs.MYSQL_URL) 38 | .hasArg() 39 | .desc(CliArgs.MYSQL_URL_DESC) 40 | .build()); 41 | options.addOption(Option.builder() 42 | .longOpt(CliArgs.MYSQL_USERNAME) 43 | .hasArg() 44 | .desc(CliArgs.MYSQL_USERNAME_DESC) 45 | .build()); 46 | options.addOption(Option.builder() 47 | .longOpt(CliArgs.MYSQL_PASSWORD) 48 | .hasArg() 49 | .desc(CliArgs.MYSQL_PASSWORD_DESC) 50 | .build()); 51 | options.addOption(Option.builder() 52 | .longOpt(CliArgs.MYSQL_MAX_CONNECTION) 53 | .hasArg() 54 | .desc(CliArgs.MYSQL_MAX_CONNECTION_DESC) 55 | .build()); 56 | options.addOption(Option.builder() 57 | .longOpt(CliArgs.USER_COUNT) 58 | .hasArg() 59 | .desc(CliArgs.USER_COUNT_DESC) 60 | .build()); 61 | options.addOption(Option.builder() 62 | .longOpt(CliArgs.MAX_ITEM_PER_USER) 63 | .hasArg() 64 | .desc(CliArgs.MAX_ITEM_PER_USER_DESC) 65 | .build()); 66 | 67 | return options; 68 | } 69 | 70 | /** 71 | * 解析命令行参数.如果解析失败,则直接退出jvm,即:{@link System#exit(int)}. 72 | * 73 | * @param options 支持的选项 74 | * @param args 命令行参数 75 | * @return {@link CliArgs}对象 76 | */ 77 | public static CliArgs parser(Options options, String[] args) { 78 | // 初始化一个命令行解析器,一般用默认的就可以 79 | CommandLineParser parser = new DefaultParser(); 80 | CliArgs cliArgs = new CliArgs(); 81 | String cmdLineSyntax = "支持的选项"; 82 | try { 83 | CommandLine commandLine = parser.parse(options, args); 84 | helpOption(options, commandLine, cmdLineSyntax); 85 | threadOption(commandLine, cliArgs); 86 | mysqlUrlOption(commandLine, cliArgs); 87 | mysqlUsernameOption(commandLine, cliArgs); 88 | mysqlPasswordOption(commandLine, cliArgs); 89 | mysqlMaxConnectionOption(commandLine, cliArgs); 90 | userCountOption(commandLine, cliArgs); 91 | maxItemPerUserOption(commandLine, cliArgs); 92 | } catch (Exception e) { 93 | // 因为本来就是java命令启动的程序,还有很多其他java自身的选项, 94 | // 最坏情况就是不识别的选项不处理而已 95 | HelpFormatter helpFormatter = new HelpFormatter(); 96 | if (!e.getClass().equals(org.apache.commons.cli.UnrecognizedOptionException.class)) { 97 | helpFormatter.printHelp(cmdLineSyntax, options); 98 | System.out.println("\n命令行参数解析异常," + e.getMessage()); 99 | exit0(); 100 | } else { 101 | helpFormatter.printHelp(cmdLineSyntax, options); 102 | logger.warn(e.getMessage(), e); 103 | } 104 | } 105 | return cliArgs; 106 | } 107 | 108 | private static void helpOption(Options options, CommandLine commandLine, String cmdLineSyntax) { 109 | // 如果存在 -h --help 参数 110 | if (commandLine.hasOption(CliArgs.HELP)) { 111 | HelpFormatter helpFormatter = new HelpFormatter(); 112 | helpFormatter.printHelp(cmdLineSyntax, options); 113 | exit0(); 114 | } 115 | } 116 | 117 | private static void threadOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException { 118 | if (commandLine.hasOption(CliArgs.THREAD)) { 119 | String optionValue = commandLine.getOptionValue(CliArgs.THREAD); 120 | if (StringUtils.isNotBlank(optionValue)) { 121 | String message = "选项" + CliArgs.THREAD + "只支持数字类型,并且必须大于0"; 122 | try { 123 | int thread = Integer.parseInt(optionValue); 124 | if (thread <= 0) { 125 | throw new ParseException(message); 126 | } 127 | cliArgs.setThread(thread); 128 | } catch (Exception e) { 129 | throw new ParseException(message); 130 | } 131 | } 132 | } 133 | } 134 | 135 | private static void mysqlUrlOption(CommandLine commandLine, CliArgs cliArgs) { 136 | if (commandLine.hasOption(CliArgs.MYSQL_URL)) { 137 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_URL); 138 | if (StringUtils.isNotBlank(optionValue)) { 139 | cliArgs.setMysqlUrl(optionValue); 140 | } 141 | } 142 | } 143 | 144 | private static void mysqlUsernameOption(CommandLine commandLine, CliArgs cliArgs) { 145 | if (commandLine.hasOption(CliArgs.MYSQL_USERNAME)) { 146 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_USERNAME); 147 | if (StringUtils.isNotBlank(optionValue)) { 148 | cliArgs.setMysqlUsername(optionValue); 149 | } 150 | } 151 | } 152 | 153 | private static void mysqlPasswordOption(CommandLine commandLine, CliArgs cliArgs) { 154 | if (commandLine.hasOption(CliArgs.MYSQL_PASSWORD)) { 155 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_PASSWORD); 156 | if (StringUtils.isNotBlank(optionValue)) { 157 | cliArgs.setMysqlPassword(optionValue); 158 | } 159 | } 160 | } 161 | 162 | private static void mysqlMaxConnectionOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException { 163 | if (commandLine.hasOption(CliArgs.MYSQL_MAX_CONNECTION)) { 164 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_MAX_CONNECTION); 165 | if (StringUtils.isNotBlank(optionValue)) { 166 | String message = "选项" + CliArgs.MYSQL_MAX_CONNECTION + "只支持数字类型,并且必须大于0"; 167 | try { 168 | int maxConnection = Integer.parseInt(optionValue); 169 | if (maxConnection <= 0) { 170 | throw new ParseException(message); 171 | } 172 | cliArgs.setMysqlMaxConnection(maxConnection); 173 | } catch (Exception e) { 174 | throw new ParseException(message); 175 | } 176 | } 177 | } 178 | } 179 | 180 | private static void userCountOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException { 181 | if (commandLine.hasOption(CliArgs.USER_COUNT)) { 182 | String optionValue = commandLine.getOptionValue(CliArgs.USER_COUNT); 183 | if (StringUtils.isNotBlank(optionValue)) { 184 | String message = "选项" + CliArgs.USER_COUNT + "只支持数字类型,并且必须大于0"; 185 | try { 186 | int userCount = Integer.parseInt(optionValue); 187 | if (userCount <= 0) { 188 | throw new ParseException(message); 189 | } 190 | cliArgs.setUserCount(userCount); 191 | } catch (Exception e) { 192 | throw new ParseException(message); 193 | } 194 | } 195 | } 196 | } 197 | 198 | private static void maxItemPerUserOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException { 199 | if (commandLine.hasOption(CliArgs.MAX_ITEM_PER_USER)) { 200 | String optionValue = commandLine.getOptionValue(CliArgs.MAX_ITEM_PER_USER); 201 | if (StringUtils.isNotBlank(optionValue)) { 202 | String message = "选项" + CliArgs.MAX_ITEM_PER_USER + "只支持数字类型,并且必须大于0"; 203 | try { 204 | int maxItemPerUser = Integer.parseInt(optionValue); 205 | if (maxItemPerUser <= 0) { 206 | throw new ParseException(message); 207 | } 208 | cliArgs.setMaxItemPerUser(maxItemPerUser); 209 | } catch (Exception e) { 210 | throw new ParseException(message); 211 | } 212 | } 213 | } 214 | } 215 | 216 | private static void exit0() { 217 | System.exit(0); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/FoundationBasedDataSet.java: -------------------------------------------------------------------------------- 1 | package com.wuda.tester.mysql.generate; 2 | 3 | import com.wuda.foundation.commons.CreateEmail; 4 | import com.wuda.foundation.commons.CreatePhone; 5 | import com.wuda.foundation.commons.property.CreatePropertyKey; 6 | import com.wuda.foundation.commons.property.CreatePropertyValue; 7 | import com.wuda.foundation.item.CreateItem; 8 | import com.wuda.foundation.item.CreateItemDescription; 9 | import com.wuda.foundation.item.CreateItemGeneral; 10 | import com.wuda.foundation.item.CreateItemVariation; 11 | import com.wuda.foundation.lang.Constant; 12 | import com.wuda.foundation.lang.FoundationContext; 13 | import com.wuda.foundation.lang.identify.BuiltinIdentifierType; 14 | import com.wuda.foundation.lang.identify.LongIdentifier; 15 | import com.wuda.foundation.store.BindStoreUser; 16 | import com.wuda.foundation.store.CreateStore; 17 | import com.wuda.foundation.store.CreateStoreGeneral; 18 | import com.wuda.foundation.user.BindUserEmail; 19 | import com.wuda.foundation.user.BindUserPhone; 20 | import com.wuda.foundation.user.CreateUser; 21 | import com.wuda.foundation.user.CreateUserAccount; 22 | import com.wuda.tester.mysql.cli.CliArgs; 23 | import com.wuda.tester.mysql.commons.utils.RandomUtilsExt; 24 | import lombok.Getter; 25 | import org.apache.commons.lang3.RandomUtils; 26 | import org.apache.commons.lang3.tuple.ImmutablePair; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * 生成foundation项目需要的数据.. 33 | * 34 | * @author wuda 35 | */ 36 | @Getter 37 | public class FoundationBasedDataSet implements DataSet { 38 | 39 | private int batchSize = 500; 40 | private byte byte0 = (byte) 0; 41 | 42 | private List users; 43 | private List userAccounts; 44 | private List emails; 45 | private List bindUserEmails; 46 | private List phones; 47 | private List bindUserPhones; 48 | private List stores; 49 | private List bindStoreUsers; 50 | private List storeGenerals; 51 | private List items; 52 | private List itemGenerals; 53 | private List itemVariations; 54 | private List itemDescriptions; 55 | private List propertyKeys; 56 | private List propertyValues; 57 | 58 | @Override 59 | public void prepare(CliArgs cliArgs) { 60 | users = newCreateUserList(); 61 | userAccounts = newCreateUserAccountList(users); 62 | ImmutablePair, List> userEmail = newUserEmail(users); 63 | emails = userEmail.left; 64 | bindUserEmails = userEmail.right; 65 | ImmutablePair, List> userPhone = newUserPhone(users); 66 | phones = userPhone.left; 67 | bindUserPhones = userPhone.right; 68 | ImmutablePair, List> userStore = newUserStore(users); 69 | stores = userStore.left; 70 | bindStoreUsers = userStore.right; 71 | storeGenerals = newCreateStoreGeneralList(stores); 72 | items = newItemList(stores, cliArgs.getMaxItemPerUser()); 73 | itemGenerals = newItemGeneralList(items); 74 | itemVariations = newItemVariationList(items); 75 | itemDescriptions = newItemDescriptionList(items); 76 | propertyKeys = newPropertyKeyList(items); 77 | propertyValues = newPropertyValueList(propertyKeys); 78 | } 79 | 80 | private List newCreateUserList() { 81 | List list = new ArrayList<>(batchSize); 82 | Long[] ids = FoundationContext.getLongKeyGenerator().next(batchSize); 83 | for (int i = 0; i < ids.length; i++) { 84 | long id = ids[i]; 85 | CreateUser user = new CreateUser.Builder() 86 | .setId(id) 87 | .setUserType(byte0) 88 | .setUserState(byte0) 89 | .build(); 90 | list.add(user); 91 | } 92 | return list; 93 | } 94 | 95 | private List newCreateUserAccountList(List users) { 96 | List list = new ArrayList<>(users.size()); 97 | Long[] ids = FoundationContext.getLongKeyGenerator().next(users.size()); 98 | for (int i = 0; i < users.size(); i++) { 99 | CreateUser user = users.get(i); 100 | long id = ids[i]; 101 | CreateUserAccount userAccount = new CreateUserAccount.Builder() 102 | .setId(id) 103 | .setUserId(user.getId()) 104 | .setPassword(RandomUtilsExt.enRandomString(10)) 105 | .setUsername(RandomUtilsExt.enRandomString(9)) 106 | .setState(byte0) 107 | .build(); 108 | list.add(userAccount); 109 | } 110 | return list; 111 | } 112 | 113 | private ImmutablePair, List> newUserEmail(List users) { 114 | List emailList = new ArrayList<>(users.size()); 115 | List bindingList = new ArrayList<>(users.size()); 116 | Long[] emailIds = FoundationContext.getLongKeyGenerator().next(users.size()); 117 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size()); 118 | for (int i = 0; i < users.size(); i++) { 119 | CreateUser user = users.get(i); 120 | long emailId = emailIds[i]; 121 | CreateEmail email = new CreateEmail.Builder() 122 | .setId(emailId) 123 | .setAddress(RandomUtilsExt.enRandomString(10) + RandomUtilsExt.randomEmailSuffix()) 124 | .setEmailState(byte0) 125 | .build(); 126 | 127 | long bindingId = bindingIds[i]; 128 | BindUserEmail bindUserEmail = new BindUserEmail.Builder() 129 | .setId(bindingId) 130 | .setEmailId(emailId) 131 | .setUserId(user.getId()) 132 | .setUse(byte0) 133 | .setState(byte0) 134 | .build(); 135 | emailList.add(email); 136 | bindingList.add(bindUserEmail); 137 | } 138 | return new ImmutablePair<>(emailList, bindingList); 139 | } 140 | 141 | private ImmutablePair, List> newUserPhone(List users) { 142 | List phoneList = new ArrayList<>(users.size()); 143 | List bindingList = new ArrayList<>(users.size()); 144 | Long[] phoneIds = FoundationContext.getLongKeyGenerator().next(users.size()); 145 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size()); 146 | for (int i = 0; i < users.size(); i++) { 147 | CreateUser user = users.get(i); 148 | long phoneId = phoneIds[i]; 149 | CreatePhone phone = new CreatePhone.Builder() 150 | .setId(phoneId) 151 | .setNumber(RandomUtilsExt.enRandomString(11)) 152 | .setPhoneType(byte0) 153 | .setPhoneState(byte0) 154 | .build(); 155 | 156 | long bindingId = bindingIds[i]; 157 | BindUserPhone bindUserPhone = new BindUserPhone.Builder() 158 | .setId(bindingId) 159 | .setPhoneId(phoneId) 160 | .setUserId(user.getId()) 161 | .setUse(byte0) 162 | .setState(byte0) 163 | .build(); 164 | phoneList.add(phone); 165 | bindingList.add(bindUserPhone); 166 | } 167 | return new ImmutablePair<>(phoneList, bindingList); 168 | } 169 | 170 | private ImmutablePair, List> newUserStore(List users) { 171 | List storeList = new ArrayList<>(users.size()); 172 | List bindingList = new ArrayList<>(users.size()); 173 | Long[] storeIds = FoundationContext.getLongKeyGenerator().next(users.size()); 174 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size()); 175 | for (int i = 0; i < users.size(); i++) { 176 | CreateUser user = users.get(i); 177 | long storeId = storeIds[i]; 178 | CreateStore store = new CreateStore.Builder() 179 | .setId(storeId) 180 | .setStoreType(byte0) 181 | .setStoreState(byte0) 182 | .build(); 183 | 184 | long bindingId = bindingIds[i]; 185 | BindStoreUser bindStoreUser = new BindStoreUser.Builder() 186 | .setId(bindingId) 187 | .setUserId(user.getId()) 188 | .setStoreId(storeId) 189 | .isStoreOwner(true) 190 | .build(); 191 | storeList.add(store); 192 | bindingList.add(bindStoreUser); 193 | } 194 | return new ImmutablePair<>(storeList, bindingList); 195 | } 196 | 197 | private List newCreateStoreGeneralList(List stores) { 198 | List list = new ArrayList<>(stores.size()); 199 | Long[] ids = FoundationContext.getLongKeyGenerator().next(stores.size()); 200 | for (int i = 0; i < stores.size(); i++) { 201 | CreateStore store = stores.get(i); 202 | long id = ids[i]; 203 | CreateStoreGeneral storeGeneral = new CreateStoreGeneral.Builder() 204 | .setId(id) 205 | .setStoreId(store.getId()) 206 | .setStoreName(RandomUtilsExt.enRandomString(5)) 207 | .build(); 208 | list.add(storeGeneral); 209 | } 210 | return list; 211 | } 212 | 213 | private List newItemList(List stores, int perStore) { 214 | List list = new ArrayList<>(stores.size()); 215 | for (int i = 0; i < stores.size(); i++) { 216 | CreateStore store = stores.get(i); 217 | int actualPerStore = RandomUtils.nextInt(1, perStore); 218 | Long[] ids = FoundationContext.getLongKeyGenerator().next(actualPerStore); 219 | for (int size = 0; size < actualPerStore; size++) { // 为每个店铺生成不同数量的item 220 | long id = ids[size]; 221 | CreateItem item = new CreateItem.Builder() 222 | .setId(id) 223 | .setStoreId(store.getId()) 224 | .setCategoryId(1L) 225 | .setItemState(byte0) 226 | .setItemType(byte0) 227 | .build(); 228 | list.add(item); 229 | } 230 | } 231 | return list; 232 | } 233 | 234 | private List newItemGeneralList(List items) { 235 | List list = new ArrayList<>(items.size()); 236 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size()); 237 | for (int i = 0; i < items.size(); i++) { 238 | CreateItem item = items.get(i); 239 | long id = ids[i]; 240 | CreateItemGeneral itemGeneral = new CreateItemGeneral.Builder() 241 | .setId(id) 242 | .setItemId(item.getId()) 243 | .setName(RandomUtilsExt.enRandomString(8)) 244 | .build(); 245 | list.add(itemGeneral); 246 | } 247 | return list; 248 | } 249 | 250 | private List newItemVariationList(List items) { 251 | List list = new ArrayList<>(items.size()); 252 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size()); 253 | for (int i = 0; i < items.size(); i++) { 254 | CreateItem item = items.get(i); 255 | long id = ids[i]; 256 | CreateItemVariation itemVariation = new CreateItemVariation.Builder() 257 | .setId(id) 258 | .setItemId(item.getId()) 259 | .setName(RandomUtilsExt.enRandomString(8)) 260 | .setState(byte0) 261 | .build(); 262 | list.add(itemVariation); 263 | } 264 | return list; 265 | } 266 | 267 | private List newItemDescriptionList(List items) { 268 | List list = new ArrayList<>(items.size()); 269 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size()); 270 | for (int i = 0; i < items.size(); i++) { 271 | CreateItem item = items.get(i); 272 | long id = ids[i]; 273 | CreateItemDescription itemDescription = new CreateItemDescription.Builder() 274 | .setId(id) 275 | .setItemId(item.getId()) 276 | .setItemVariationId(Constant.NOT_EXISTS_ID) 277 | .setContent(RandomUtilsExt.enRandomString(8)) 278 | .build(); 279 | list.add(itemDescription); 280 | } 281 | return list; 282 | } 283 | 284 | private List newPropertyKeyList(List items) { 285 | List list = new ArrayList<>(items.size()); 286 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size()); 287 | for (int i = 0; i < items.size(); i++) { 288 | CreateItem item = items.get(i); 289 | long id = ids[i]; 290 | CreatePropertyKey createPropertyKey = new CreatePropertyKey.Builder() 291 | .setId(id) 292 | .setOwner(new LongIdentifier(item.getId(), BuiltinIdentifierType.TABLE_ITEM)) 293 | .setType(byte0) 294 | .setUse(byte0) 295 | .setKey(RandomUtilsExt.enRandomString(6)) 296 | .build(); 297 | list.add(createPropertyKey); 298 | } 299 | return list; 300 | } 301 | 302 | private List newPropertyValueList(List propertyKeys) { 303 | List list = new ArrayList<>(propertyKeys.size()); 304 | Long[] ids = FoundationContext.getLongKeyGenerator().next(propertyKeys.size()); 305 | for (int i = 0; i < propertyKeys.size(); i++) { 306 | CreatePropertyKey propertyKey = propertyKeys.get(i); 307 | long id = ids[i]; 308 | CreatePropertyValue createPropertyValue = new CreatePropertyValue.Builder() 309 | .setId(id) 310 | .setPropertyKeyId(propertyKey.getId()) 311 | .setValue(RandomUtilsExt.enRandomString(8)) 312 | .build(); 313 | list.add(createPropertyValue); 314 | } 315 | return list; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /mysql_tester.sql: -------------------------------------------------------------------------------- 1 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 2 | /*!40101 SET NAMES utf8 */; 3 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 4 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 5 | 6 | CREATE DATABASE IF NOT EXISTS `foundation_commons`; 7 | USE `foundation_commons`; 8 | 9 | CREATE TABLE IF NOT EXISTS `email` ( 10 | `email_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 11 | `address` varchar(20) NOT NULL COMMENT 'email address', 12 | `state` tinyint(3) unsigned NOT NULL COMMENT '邮箱状态。1比如:验证不通过,验证通过,未验证', 13 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 14 | `create_user_id` bigint(20) unsigned NOT NULL, 15 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 16 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 17 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 18 | PRIMARY KEY (`email_id`), 19 | UNIQUE KEY `idx_email_unique` (`address`,`is_deleted`) 20 | ) ENGINE=InnoDB AUTO_INCREMENT=314876056249048299 COMMENT='email信息'; 21 | 22 | CREATE TABLE IF NOT EXISTS `phone` ( 23 | `phone_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 24 | `number` varchar(20) NOT NULL COMMENT '电话号码', 25 | `type` tinyint(3) unsigned NOT NULL COMMENT '电话类型。用于区分手机,-固话等。注意:不是用来区分【客服电话还是400电话】这种类型,这些属于业务,应该由具体的业务表关联到这个表。', 26 | `state` tinyint(3) unsigned NOT NULL COMMENT '手机状态', 27 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 28 | `create_user_id` bigint(20) unsigned NOT NULL, 29 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 30 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 31 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 32 | PRIMARY KEY (`phone_id`), 33 | UNIQUE KEY `idx_phone_number` (`number`,`is_deleted`) 34 | ) ENGINE=InnoDB AUTO_INCREMENT=314875826682205361 COMMENT='电话信息。'; 35 | 36 | CREATE TABLE IF NOT EXISTS `property_key` ( 37 | `property_key_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 38 | `key` varchar(45) NOT NULL COMMENT '属性名', 39 | `type` tinyint(3) unsigned NOT NULL COMMENT '属性的类型,比如最常用的就是“字面量”类型;比如该属性表示图片,属性值保存图片的链接;比如该属性表示颜色,因为在一些应用中,可以使用调色盘选取颜色,或者在显示时,可以显示颜色,而不是白色这样的纯文本', 40 | `owner_type` tinyint(3) unsigned NOT NULL COMMENT '该属性的owner的类型', 41 | `owner_identifier` bigint(20) unsigned NOT NULL COMMENT '该属性的owner的id', 42 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 43 | `create_user_id` bigint(20) unsigned NOT NULL, 44 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 45 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 46 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 47 | PRIMARY KEY (`property_key_id`), 48 | UNIQUE KEY `idx_unique` (`owner_identifier`,`owner_type`,`key`,`is_deleted`) 49 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986349204186 COMMENT='属性的key'; 50 | 51 | CREATE TABLE IF NOT EXISTS `property_value` ( 52 | `property_value_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 53 | `property_key_id` bigint(20) unsigned NOT NULL COMMENT '所属的key', 54 | `value` varchar(45) NOT NULL COMMENT '属性值', 55 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 56 | `create_user_id` bigint(20) unsigned NOT NULL, 57 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 58 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 59 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 60 | PRIMARY KEY (`property_value_id`), 61 | UNIQUE KEY `idx_attr_value` (`property_key_id`,`value`,`is_deleted`) 62 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986353399087 COMMENT='属性的值'; 63 | 64 | CREATE DATABASE IF NOT EXISTS `foundation_item`; 65 | USE `foundation_item`; 66 | 67 | CREATE TABLE IF NOT EXISTS `item` ( 68 | `item_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 69 | `store_id` bigint(20) unsigned NOT NULL COMMENT '所属店铺ID', 70 | `type` tinyint(3) unsigned NOT NULL COMMENT '商品类型 . 不同类型的商品, 保存到各自不同的表中. 参考 https://learnwoo.com/woocommerce-different-product-types/', 71 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态', 72 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 73 | `create_user_id` bigint(20) unsigned NOT NULL, 74 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 75 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 76 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 77 | PRIMARY KEY (`item_id`), 78 | KEY `fk_store_id` (`store_id`) 79 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986730886848 COMMENT='代表所有的物品,之前有把用户ID放进来,表示该物品所属的用户,但是考虑到如果有子账号的情况,物品难道属于这个子账号所属的用户吗?而且记录了创建人用户ID,考虑这两个因素,因此不设置用户ID列'; 80 | 81 | CREATE TABLE IF NOT EXISTS `item_description` ( 82 | `item_description_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 83 | `item_id` bigint(20) unsigned NOT NULL, 84 | `item_variation_id` bigint(20) unsigned NOT NULL DEFAULT '0', 85 | `content` mediumtext COMMENT '描述内容', 86 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 87 | `create_user_id` bigint(20) unsigned NOT NULL, 88 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 89 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 90 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 91 | PRIMARY KEY (`item_description_id`), 92 | KEY `fk_item_id` (`item_id`), 93 | KEY `fk_item_variation_id` (`item_variation_id`) 94 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986345009285 COMMENT='item的描述信息,通常作为详情的一个字段,但是,由于描述信息通常内容较多,很多orm框架都是select *,分开了可以避免查询出来(有时候根本就没用到),而且大数据量的字段更新性能较差,如果需要更新,会影响到核心item表,因此单独作为一个表保存。也可以表示物品某个规格的描述信息,如果variation id不等于0'; 95 | 96 | CREATE TABLE IF NOT EXISTS `item_general` ( 97 | `item_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 98 | `item_id` bigint(20) unsigned NOT NULL, 99 | `name` varchar(45) NOT NULL, 100 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 101 | `create_user_id` bigint(20) unsigned NOT NULL, 102 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 103 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 104 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 105 | PRIMARY KEY (`item_general_id`), 106 | KEY `fk_item_id` (`item_id`) 107 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986571504463 COMMENT='物品基本信息,也可以表示物品某个规格的基本信息,如果variation id不等于0'; 108 | 109 | CREATE TABLE IF NOT EXISTS `item_variation` ( 110 | `item_variation_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 111 | `item_id` bigint(20) unsigned NOT NULL, 112 | `name` varchar(45) NOT NULL DEFAULT '' COMMENT '规格名称。方便管理。', 113 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态', 114 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 115 | `create_user_id` bigint(20) unsigned NOT NULL, 116 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 117 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 118 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 119 | PRIMARY KEY (`item_variation_id`), 120 | KEY `fk_item_id` (`item_id`) 121 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986575699859 COMMENT='规格。比如一件衣服,有红色,白色两种规格。具体的属性和值保存在MongoDB. 不能用属性ID关联, 而是要具体的属性名称和值, 避免关联的属性修改. 注意和SKU之间的区别.'; 122 | 123 | CREATE DATABASE IF NOT EXISTS `foundation_store`; 124 | USE `foundation_store`; 125 | 126 | CREATE TABLE IF NOT EXISTS `store` ( 127 | `store_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 128 | `type` tinyint(3) unsigned NOT NULL COMMENT '店铺类型', 129 | `state` tinyint(3) unsigned NOT NULL COMMENT '店铺状态', 130 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 131 | `create_user_id` bigint(20) unsigned NOT NULL, 132 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 133 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 134 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 135 | PRIMARY KEY (`store_id`) 136 | ) ENGINE=InnoDB AUTO_INCREMENT=314875818503314715 COMMENT='店铺信息'; 137 | 138 | CREATE TABLE IF NOT EXISTS `store_general` ( 139 | `store_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 140 | `store_id` bigint(20) unsigned NOT NULL COMMENT '主键', 141 | `store_name` varchar(45) NOT NULL COMMENT '店铺名称', 142 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 143 | `create_user_id` bigint(20) unsigned NOT NULL, 144 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 145 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 146 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 147 | PRIMARY KEY (`store_general_id`), 148 | KEY `fk_store_id` (`store_id`) 149 | ) ENGINE=InnoDB AUTO_INCREMENT=314875810760626565 COMMENT='店铺基本信息'; 150 | 151 | CREATE TABLE IF NOT EXISTS `store_user_relationship` ( 152 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 153 | `store_id` bigint(20) unsigned NOT NULL COMMENT 'store id', 154 | `user_id` bigint(20) unsigned NOT NULL COMMENT 'user id', 155 | `is_store_owner` bit(1) NOT NULL COMMENT '该用户是否店铺的owner。一个店铺只能有一个owner,就好像在store表中放入user id字段,表明一对一的关系一样。', 156 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 157 | `create_user_id` bigint(20) unsigned NOT NULL, 158 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 159 | PRIMARY KEY (`id`), 160 | KEY `fk_store_id` (`store_id`), 161 | KEY `fk_user_id` (`user_id`) 162 | ) ENGINE=InnoDB AUTO_INCREMENT=314875816137724979 COMMENT='如果把用户ID字段放在store表中,表明店铺属于某个用户,但是如果有多个用户可以管理这个店铺呢?有种做法是一个用户作为另一个用户的子账号;也可以建立用户与店铺的关联关系,这样感觉更符合逻辑。把用户IID放在store表,可以很明确的表明店铺的owner,如果是用关联关系表的话,就需要明确的标明哪个用户是owner,哪些用户只是管理这个店铺的。'; 163 | 164 | CREATE DATABASE IF NOT EXISTS `foundation_user` ; 165 | USE `foundation_user`; 166 | 167 | CREATE TABLE IF NOT EXISTS `individual_user_general` ( 168 | `individual_user_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 169 | `user_id` bigint(20) unsigned NOT NULL, 170 | `nickname` varchar(20) DEFAULT NULL COMMENT '昵称', 171 | `biography` varchar(128) DEFAULT NULL COMMENT '个人简介。简短的介绍', 172 | `picture` varchar(45) DEFAULT NULL COMMENT '用户图像。保存的是图片地址。命名来源:github', 173 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 174 | `create_user_id` bigint(20) unsigned NOT NULL, 175 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 176 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 177 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 178 | PRIMARY KEY (`individual_user_general_id`), 179 | KEY `fk_user_id` (`user_id`) 180 | ) ENGINE=InnoDB COMMENT='个人用户-基本信息'; 181 | 182 | CREATE TABLE IF NOT EXISTS `user` ( 183 | `user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 184 | `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户类型', 185 | `state` tinyint(3) unsigned NOT NULL COMMENT '用户状态', 186 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 187 | `create_user_id` bigint(20) unsigned NOT NULL, 188 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 189 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 190 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 191 | PRIMARY KEY (`user_id`) 192 | ) ENGINE=InnoDB AUTO_INCREMENT=314876148024612889 COMMENT='用户有很多类型,比如一种分类方法是把用户分成个人用户和企业用户,而不同类型的用户需要的字段不一样,但是他们都是用户,即 is-a user。这个表属于所有用户的基本信息,其他不同类型的用户有自己专属的表,然后用用户ID关联回这个表。这样做还有一个好处,那就是其他表中的用户ID都统一关联回这个表,这样用户ID就不会有歧义了。'; 193 | 194 | CREATE TABLE IF NOT EXISTS `user_account` ( 195 | `user_account_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 196 | `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID', 197 | `username` varchar(45) NOT NULL COMMENT '只能是英文模式下的字母,数字,下划线,中划线,必须明确检查保证不是邮箱。设置以后不能修改(github可以修改),可用作用户主页URL的一部分,参考github。注意和昵称的区别', 198 | `password` varchar(45) NOT NULL COMMENT '只能是ASCII表中的可打印字符', 199 | `state` tinyint(3) unsigned NOT NULL COMMENT '账号的状态', 200 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 201 | `create_user_id` bigint(20) unsigned NOT NULL, 202 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 203 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 204 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 205 | PRIMARY KEY (`user_account_id`), 206 | UNIQUE KEY `idx_username` (`username`,`is_deleted`) 207 | ) ENGINE=InnoDB AUTO_INCREMENT=314876128948918487 COMMENT='用户账号信息,适用各种类型的用户'; 208 | 209 | CREATE TABLE IF NOT EXISTS `user_email` ( 210 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 211 | `user_id` bigint(20) unsigned NOT NULL, 212 | `email_id` bigint(20) unsigned NOT NULL, 213 | `use` tinyint(3) unsigned NOT NULL COMMENT '用途。比如用于登录', 214 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态,每种用途的email状态可能不同,比如如果用于登录的email,状态可以是禁止登录状态', 215 | `description` varchar(45) DEFAULT NULL COMMENT '简单描述', 216 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 217 | `create_user_id` bigint(20) unsigned NOT NULL, 218 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 219 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 220 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 221 | PRIMARY KEY (`id`), 222 | UNIQUE KEY `idx_unique` (`email_id`,`use`,`is_deleted`), 223 | KEY `fk_user_id` (`user_id`) 224 | ) ENGINE=InnoDB AUTO_INCREMENT=314875924648564415 COMMENT='用户的email'; 225 | 226 | CREATE TABLE IF NOT EXISTS `user_phone` ( 227 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 228 | `user_id` bigint(20) unsigned NOT NULL, 229 | `phone_id` bigint(20) unsigned NOT NULL, 230 | `use` tinyint(3) unsigned NOT NULL COMMENT '电话的用途。比如用于400电话。也就是电话使用的业务场景。', 231 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态,每种用途的phone的il状态可能不同,比如如果用于登录的phone,状态可以是禁止登录状态', 232 | `description` varchar(45) DEFAULT NULL COMMENT '简单描述', 233 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 234 | `create_user_id` bigint(20) unsigned NOT NULL, 235 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 236 | `last_modify_user_id` bigint(20) unsigned NOT NULL, 237 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0', 238 | PRIMARY KEY (`id`), 239 | UNIQUE KEY `idx_unique` (`phone_id`,`use`,`is_deleted`), 240 | KEY `fk_user_id` (`user_id`) 241 | ) ENGINE=InnoDB AUTO_INCREMENT=314875826682205861 COMMENT='用户的电话'; 242 | 243 | /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; 244 | /*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; 245 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; --------------------------------------------------------------------------------