Appearance
系统设计
此部分针对整个项目的系统设计,包含整个系统的架构设计,数据库表设计;系统交互时序;缓存体系设计
建模
ER图

数据表
奖品表(card_product)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| name | varchar(255) | 奖品名称 |
| pic | varchar(255) | 图片 |
| info | varchar(1000) | 简介 |
| price | decimal(10,2) | 市场价 |
活动表(card_game)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| title | varchar(255) | 活动主题 |
| pic | varchar(255) | 活动宣传图 |
| info | varchar(1000) | 活动简介 |
| starttime | datetime | 开始时间 |
| endtime | datetime | 结束时间 |
| type | tinyint(2) | 类型(1=概率类,2=随机类) |
| status | tinyint(1) | 状态(0=新建,1=已加载) |
会员表(card_user)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(11) unsigned | |
| uname | varchar(20) | 用户名 |
| passwd | varchar(50) | 密码 |
| realname | varchar(10) | 姓名 |
| idcard | varchar(18) | 身份证号 |
| phone | varchar(15) | 手机号码 |
| level | smallint(6) | 等级 |
| createtime | datetime | 注册时间 |
| updatetime | datetime | 更新时间 |
策略表(card_game_rules)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(11) unsigned | |
| gameid | int(11) unsigned | 活动id |
| userlevel | smallint(6) | 会员等级 |
| enter_times | smallint(6) | 可抽奖次数(0为不限) |
| goal_times | smallint(6) | 最大中奖次数(0为不限) |
中奖纪录(card_user_hit)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| gameid | int(10) unsigned | 活动id |
| userid | int(10) unsigned | 用户 |
| productid | int(10) unsigned | 奖品 |
| hittime | datetime | 中奖时间 |
奖品活动关联关系(card_game_product)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| gameid | int(11) unsigned | 活动id |
| productid | int(11) unsigned | 奖品id |
| amount | smallint(6) | 数量 |
视图
中奖信息(view_card_user_hit)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| title | varchar(255) | 活动主题 |
| type | varchar(100) | 类型(值) |
| uname | varchar(20) | 用户名 |
| realname | varchar(10) | 姓名 |
| idcard | varchar(18) | 身份证号 |
| phone | varchar(15) | 手机号码 |
| level | varchar(100) | 会员等级(值) |
| name | varchar(255) | 奖品名称 |
| price | decimal(10,2) | 市场价 |
| gameid | int(10) unsigned | 活动 |
| userid | int(10) unsigned | 用户 |
| productid | int(10) unsigned | 奖品 |
| hittime | datetime | 中奖时间 |
奖品数统计(view_game_curinfo)
| 字段 | 类型 | 备注 |
|---|---|---|
| id | int(10) unsigned | |
| title | varchar(255) | 活动主题 |
| type | varchar(100) | 类型(值) |
| starttime | datetime | 开始时间 |
| endtime | datetime | 结束时间 |
| type | varchar(100) | 类型(值) |
| total | decimal(27,0) | |
| hit | bigint(21) |
概要设计
业务系统架构拓扑图

软件系统架构拓扑图

设计原则
动静分离
- 静态文件分离,nginx直接响应,不要再绕后台应用机器
微服务化
- 将模块细粒度拆分,微服务化
- 借助 docker swarm 的容器管理功能,实现不同服务的副本部署,滚动更新
- 在本项目中,api 模块就部署了3份,以适应前端的高并发
负载均衡
- 多个实例之间通过 nginx 做负载均衡,提升并发性能
- 本项目模块均部署在1台节点。生产环境涉及多台机器,用通过
upstream配置实现。
异步消息
- 中奖后,中奖人及奖品信息要持久化到数据库。引入 rabbitmq,将抽奖操作与数据库操作异步隔离。
- 抽奖中奖后,只需要将中奖信息放入 rabbitmq,并立即返回中奖信息给前端用户。
- 后端 msg 模块消费 rabbitmq 消息,缓慢处理。
缓存预热
- 每隔 1 分钟扫描一次活动表,查询未来 1 分钟内将要开始的活动。
- 将扫到的活动加载进 redis,包括活动详细信息,中奖策略信息,奖品信息,抽奖令牌。
- 活动正式开始后,基于 redis 数据做查询,不必再与数据库打交道。
项目交互序列图

缓存体系
缓存体系概览图:

缓存结构设计:
- 活动开始前 1 分钟扫描将要开始的活动
- 将活动信息加载进 redis
- 将活动策略信息加载进 redis
- 按活动奖品信息,生成对应个数的时间戳做令牌,从小到大排好序,右侧入队
- 以令牌为 key,对应的奖品为 value,建立映射关系,为中奖后获取奖品做准备
- 抽奖开始时,从令牌队列左侧获取令牌
- 如果令牌小于当前时间,说明中奖,找到令牌对应的奖品,抽走
- 如果令牌大于当前时间,说明未中奖,从左侧将令牌还回队列
活动基本信息
k-v形式存储,以活动 id 为 key,活动对象为 value,永不超时。示例代码如下:
java
redisUtil.set(RedisKeys.INFO + game.getId(), game, ‐1);活动策略信息
hash类型存储,以 活动 id 为 key,用户等级为 hashkey,策略值为 value。示例代码如下:
java
redisUtil.hset(RedisKeys.MAXGOAL + game.getId(), r.getUserlevel() + "", r.getGoalTimes());
redisUtil.hset(RedisKeys.MAXENTER + game.getId(), r.getUserlevel() + "", r.getEnterTimes());抽奖令牌桶
使用双端队列,即 list 类型存储,以 活动 id 为 key,在活动时间段内,随机生成时间戳做令牌,有多少个奖品就生成多少个令牌。令牌即奖品发放的时间点。从小到大排序后从右侧入队。示例代码如下:
java
redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(), tokenList);奖品映射信息
k-v形式存储 , 以 活动id_令牌 为 key,奖品信息为 value,会员获取到令牌后,如果令牌有效,则用令牌 token 值,来这里获取奖品详细信息。示例代码如下:
java
redisUtil.set(RedisKeys.TOKEN + game.getId() + "_" + token, productMap.get(cgp.getProductid()), expire);令牌设计技巧
假设活动时间间隔太短,奖品数量太多。那么极有可能产生的时间戳发生重复。
解决技巧:额外再附加一个随机因子。将 (时间戳 * 1000 + 3位随机数)作为令牌。抽奖时,将抽中的令牌/1000,还原真实的时间戳。示例代码如下:
java
// 活动持续时间(ms)
long duration = end ‐ start;
long rnd = start + new Random().nextInt((int) duration);
// 为什么乘1000,再额外加一个随机数呢? ‐ 防止时间段奖品多时重复
long token = rnd * 1000 + new Random().nextInt(999);中奖计数
k-v形式存储,以 活动id_用户id 作为 key,中奖数为 value,利用 redis 原子性,中奖后使用 incr 命令增加计数。示例代码如下:
java
redisUtil.incr(RedisKeys.USERHIT + gameid + "_" + user.getId(), 1);中奖逻辑判断
抽奖时,从令牌桶左侧出队和当前时间比较,如果令牌时间戳小于等于当前时间,令牌有效,表示中奖。大于当前时间,则令牌无效,将令牌还回,从左侧压入队列。
管理后台框架选型
此管理后台直接借助开源zcurd开发平台,完成后台基本的增删改查。
zcurd 项目地址:https://gitee.com/515097842/zcurd
前台工程各模块介绍
【frontend】工程是接口微服务源码,项目基于springcloud构建微服务体系

聚合工程(frontend)
此工程是所有项目共用的依赖及版本定义。继承自 springboot 2.1.7.RELEASE,依赖 spring cloud Greenwich.SR2。以下是部分依赖片段:
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
</parent>
....
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<scope>import</scope>
<type>pom</type>
</dependency>
....
</dependencies>
</dependencyManagement>公共模块(commons)
分页、密码、统一结果dto等工具类,mybatis生成的实体,mapper,redis及rabbit的配置bean
注册中心(eureka)
cloud 官方推荐的微服务注册中心,所有的服务模块都在 eureka 中注册。
调度模块(job)
集成当当网开源的 elastic-job,活动的调度扫描预热等定时任务在该模块中。
消息模块(msg)
RabbitMQ 消息消费端,用户中奖后将中奖信息发送到 RabbitMQ,msg模块将消费消息,将中奖信息写入db。
接口模块(api)
提供给前端页面的 rest 接口模块,如:抽奖接口,活动查询接口,中奖信息接口等
微服务框架集成
集成 zookeeper 配置中心
spring cloud 配置中心使用 zookeeper,以 api 工程为例
引入 zookeeper 配置中心依赖
此依赖已经在父级聚合工程中引入:
xml
<!-- 配置中心 zookeeper -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>创建 bootstrap.properties 配置文件
在 api 项目的 resources 目录下创建 bootstrap.properties 配置文件(此配置文件的优先最高)。此文件核心的配置内容是 zookeeper 配置中心的连接地址。具体配置内容如下:
properties
## 配置应用名称
spring.application.name=api
## 配置zookeeper地址
spring.cloud.zookeeper.connect-string=192.168.12.132:2181
spring.cloud.zookeeper.config.root=prize
spring.profiles.active=dev配置查找的优先级
使用 zookeeper 配置中心的加载优先级是:
本地配置 -> `模块,环境` 节点 -> `模块` 节点 -> `application,环境` 公共节点 -> `application` 公共节点以 api 工程为例,会先加载本地配置文件 bootstrap.properties,再根据当前配置的 profiles.active 属性,加载 api,dev 节点,再加载 api 节点,再加载公共的 application,dev,最后加载公共的 application 节点


6.2.集成 Redis
引入 Spring Data Redis 依赖
本项目使用 Spring Data Redis,因为可能会有多个模块用到缓存,所以在 commons 工程引入 redis 客户端的依赖,其他工程需要时再依赖 commons 模块即可:
xml
<!-- Spring Data Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--<scope>require</scope>-->
</dependency>Redis 相关配置
在 zookeeper 配置中心中修改 Redis 的配置。此部分为公共配置,所以在 /prize/application 节点中定义。如开发时有不一样的配置,在application,dev节点下设置相同配置即可覆盖
properties
/prize/application=spring.redis.database=0
/prize/application=spring.redis.host=172.17.0.1
/prize/application=spring.redis.password=
/prize/application=spring.redis.pool.max-active=3
/prize/application=spring.redis.pool.max-idle=10
/prize/application=spring.redis.pool.max-wait=1000
/prize/application=spring.redis.pool.min-idle=1
/prize/application=spring.redis.port=6379
/prize/application=spring.redis.timeout=3000
集成 elastic-job 定时任务
引入 elastic-job 依赖
定时调度只有 job 模块会使用,所只在 job 模块工程中引入 elastic-job 的依赖
注意:curator版本会报冲突,排除后,手动引入版本
xml
<properties>
....
<job.version>2.1.5</job.version>
<curator.version>2.10.0</curator.version>
</properties>
<dependencies>
<!-- elastic-job 框架核心jar包 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>${job.version}</version>
<exclusions>
<exclusion>
<artifactId>curator-client</artifactId>
<groupId>org.apache.curator</groupId>
</exclusion>
<exclusion>
<artifactId>curator-framework</artifactId>
<groupId>org.apache.curator</groupId>
</exclusion>
<exclusion>
<artifactId>curator-recipes</artifactId>
<groupId>org.apache.curator</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 手动引入 curator 依赖,指定不会冲突的版本 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>${job.version}</version>
</dependency>
<!--添加数据相关的驱动主要是为了记录任务相关的一些数据,日志-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.moon.prize</groupId>
<artifactId>commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>elastic-job 相关配置
以下是 elastic-job 相关配置,保存到 txt 文档,通过 zkui 导入到 zookeeper 即可。
properties
/prize/job,dev=elaticjob.zookeeper.session-timeout-milliseconds=5000
/prize/job,dev=spring.datasource.druid.joblog.driver-class-name=com.mysql.jdbc.Driver
/prize/job,dev=spring.datasource.druid.joblog.password=root
/prize/job,dev=spring.datasource.druid.joblog.url=jdbc:mysql://192.168.12.132:3306/event_log
/prize/job,dev=spring.datasource.druid.joblog.username=root
/prize/job,dev=spring.jpa.database=mysql
/prize/job,dev=spring.jpa.hibernate.ddl-auto=update
/prize/job,dev=spring.jpa.show-sql=true
/prize/job=elaticjob.zookeeper.session-timeout-milliseconds=5000
/prize/job=spring.datasource.druid.joblog.driver-class-name=com.mysql.jdbc.Driver
/prize/job=spring.datasource.druid.joblog.password=root
/prize/job=spring.datasource.druid.joblog.url=jdbc:mysql://172.17.0.1:3306/event_log
/prize/job=spring.datasource.druid.joblog.username=root
/prize/job=spring.jpa.database=mysql
/prize/job=spring.jpa.hibernate.ddl-auto=update
/prize/job=spring.jpa.show-sql=true配置与使用
- 创建 elastic-job 的自定义注解
ElasticSimpleJob,用于设置任务的相关内容
java
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ElasticSimpleJob {
// cron 表达式
@AliasFor("cron")
String value() default "";
@AliasFor("value")
String cron() default "";
// 定时任务的名称
String jobName() default "";
// 分片的数量,即在分布式环境中分成多份同时执行
int shardingTotalCount() default 1;
// 指定不同分片所获取到的传递参数
String shardingItemParameters() default "";
// 执行任务所传递的参数
String jobParameter() default "";
// 定义数据源,值是数据源实例在 spring 容器中的名称
String dataSource() default "";
String description() default "";
boolean disabled() default false;
boolean overwrite() default true;
}- 创建 elastic-job 的配置类,在此类中主要是初始化 elastic-job 的任务调度对象
JobEventRdbConfiguration并调用其init初始化方法,这里所需要的参数都是从上面自定义的@ElasticSimpleJob注解中获取
java
@Configuration
public class ElasticJobAutoConfiguration {
@Value("${spring.cloud.zookeeper.connect-string}")
private String serverList;
@Value("${spring.application.name}")
private String namespace;
@Value("${elaticjob.zookeeper.session-timeout-milliseconds}")
private int sessionTimeoutMilliseconds;
@Autowired
private ApplicationContext applicationContext;
// 在配置类加载时执行,对 elastic-job 的一些配置
@PostConstruct
public void initElasticJob() {
// 获取 zookeeper 中的配置
ZookeeperConfiguration config = new ZookeeperConfiguration(serverList, namespace);
config.setSessionTimeoutMilliseconds(sessionTimeoutMilliseconds);
// 初始化 ZookeeperRegistryCenter,主要是用于 elastic-job 分片与调试
ZookeeperRegistryCenter regCenter = new ZookeeperRegistryCenter(config);
regCenter.init();
// 获取 spring 容器中所有 SimpleJob 接口的实例(实现类)
Map<String, SimpleJob> map = applicationContext.getBeansOfType(SimpleJob.class);
for (Map.Entry<String, SimpleJob> entry : map.entrySet()) {
SimpleJob simpleJob = entry.getValue();
// 获取 @ElasticSimpleJob 注解(自定义的注解)
ElasticSimpleJob elasticSimpleJobAnnotation = simpleJob.getClass().getAnnotation(ElasticSimpleJob.class);
// 获取注解中的 cron 表达式、分片、任务名称、任务的参数等,用于创建 SimpleJobConfiguration 配置对象
String cron = StringUtils.defaultIfBlank(elasticSimpleJobAnnotation.cron(), elasticSimpleJobAnnotation.value());
SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration(
JobCoreConfiguration.newBuilder(simpleJob.getClass().getName(), cron, elasticSimpleJobAnnotation.shardingTotalCount())
.shardingItemParameters(elasticSimpleJobAnnotation.shardingItemParameters())
.build(),
simpleJob.getClass().getCanonicalName()
);
LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(simpleJobConfiguration).overwrite(true).build();
String dataSourceRef = elasticSimpleJobAnnotation.dataSource();
if (StringUtils.isNotBlank(dataSourceRef)) {
if (!applicationContext.containsBean(dataSourceRef)) {
throw new RuntimeException("not exist datasource [" + dataSourceRef + "] !");
}
DataSource dataSource = (DataSource) applicationContext.getBean(dataSourceRef);
JobEventRdbConfiguration jobEventRdbConfiguration = new JobEventRdbConfiguration(dataSource);
// 创建 SpringJobScheduler 任务对象,并调用初始化方法
SpringJobScheduler jobScheduler = new SpringJobScheduler(simpleJob, regCenter, liteJobConfiguration, jobEventRdbConfiguration);
jobScheduler.init();
} else {
SpringJobScheduler jobScheduler = new SpringJobScheduler(simpleJob, regCenter, liteJobConfiguration);
jobScheduler.init();
}
}
}
/**
* 设置活动监听,前提是已经设置好了监听,见下一个目录
*
* @return
*/
@Bean
public ElasticJobListener elasticJobListener() {
return new ElasticJobListener(100, 100);
}
}- 创建
com.dangdang.ddframe.job.api.simple.SimpleJob的实现类,在此类的execute方法中编写定时任务的业务逻辑
java
/**
* 分片调度测试
* ribbon调度测试
* 启动多个实例,会发现不同分片运行到不同实例上
* 关闭后,分片被重新调度到存活的机器
* 证明elastic-job是分布式的,可分片的。
*/
@ElasticSimpleJob(cron = "0/3 * * * * ?",
jobName = "firstJob",
shardingTotalCount = 2,
jobParameter = "测试参数",
shardingItemParameters = "0=A,1=B",
dataSource = "datasource")
@Component
public class MyJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
System.out.println(String.format("jobtest------>Thread ID: %s, 任务总片数: %s, " +
"当前分片项: %s,当前参数: %s," +
"当前任务名称: %s,当前任务参数: %s,"+
"当前任务的id: %s"
,
// 获取当前线程的id
Thread.currentThread().getId(),
// 获取任务总片数
shardingContext.getShardingTotalCount(),
// 获取当前分片项
shardingContext.getShardingItem(),
// 获取当前的参数
shardingContext.getShardingParameter(),
// 获取当前的任务名称
shardingContext.getJobName(),
// 获取当前任务参数
shardingContext.getJobParameter(),
// 获取任务的id
shardingContext.getTaskId()
));
}
}注:其中
@ElasticSimpleJob是自定义的注解
集成 RabbitMQ
引入 RabbitMQ 依赖
因为可能会有多个需要发送与消费消息,所以在 commons 工程引入 RabbitMQ 的依赖,其他工程需要时再依赖 commons 模块即可:
xml
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>RabbitMQ 相关配置
以下是 RabbitMQ 相关配置,保存到 txt 文档,通过 zkui 导入到 zookeeper 即可。
properties
/prize/application,dev=spring.rabbitmq.host=192.168.12.132
/prize/application,dev=spring.rabbitmq.password=guest
/prize/application,dev=spring.rabbitmq.port=5672
/prize/application,dev=spring.rabbitmq.publisher-confirms=true
/prize/application,dev=spring.rabbitmq.username=guest
/prize/application,dev=spring.rabbitmq.virtual-host=/
/prize/application=spring.rabbitmq.host=172.17.0.1
/prize/application=spring.rabbitmq.password=guest
/prize/application=spring.rabbitmq.port=5672
/prize/application=spring.rabbitmq.publisher-confirms=true
/prize/application=spring.rabbitmq.username=guest
/prize/application=spring.rabbitmq.virtual-host=/集成 MySQL 数据源 druid
引入数据源相关依赖
在父级聚合项目中 <dependencyManagement> 进行版本控制,各自模块自行依赖即可:
xml
<!-- 数据源 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>数据源相关配置
properties
/prize/application=spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
/prize/application=spring.datasource.driver-class-name=com.mysql.jdbc.Driver
/prize/application=spring.datasource.filters=stat,wall,log4j
/prize/application=spring.datasource.initialSize=5
/prize/application=spring.datasource.maxActive=20
/prize/application=spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
/prize/application=spring.datasource.maxWait=60000
/prize/application=spring.datasource.minEvictableIdleTimeMillis=300000
/prize/application=spring.datasource.minIdle=5
/prize/application=spring.datasource.password=root
/prize/application=spring.datasource.poolPreparedStatements=true
/prize/application=spring.datasource.testOnBorrow=false
/prize/application=spring.datasource.testOnReturn=false
/prize/application=spring.datasource.testWhileIdle=true
/prize/application=spring.datasource.timeBetweenEvictionRunsMillis=60000
/prize/application=spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
/prize/application=spring.datasource.url=jdbc:mysql://172.17.0.1/prize?useUnicode=true&characterEncoding=utf-8
/prize/application=spring.datasource.useGlobalDataSourceStat=true
/prize/application=spring.datasource.username=root
/prize/application=spring.datasource.validationQuery=SELECT 'x'
/prize/job,dev=spring.datasource.druid.joblog.driver-class-name=com.mysql.jdbc.Driver
/prize/job,dev=spring.datasource.druid.joblog.password=root
/prize/job,dev=spring.datasource.druid.joblog.url=jdbc:mysql://192.168.12.132:3306/event_log
/prize/job,dev=spring.datasource.druid.joblog.username=root
/prize/job=spring.datasource.druid.joblog.driver-class-name=com.mysql.jdbc.Driver
/prize/job=spring.datasource.druid.joblog.password=root
/prize/job=spring.datasource.druid.joblog.url=jdbc:mysql://192.168.12.132:3306/event_log
/prize/job=spring.datasource.druid.joblog.username=root集成 mybatis
引入 mybatis 依赖
在父级聚合项目中 <dependencyManagement> 进行版本控制,各自模块自行依赖即可:
xml
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>mybatis 相关配置
通过 zkui 服务直接修改配置即可
properties
/prize/application,dev=mybatis.mapper-locations=classpath:mapper/*.xml
/prize/application=mybatis.mapper-locations=classpath:mapper/*.xml集成 redis session
引入 session 依赖
在 commons 工程引入 spring-session-data-redis 客户端的依赖,其他工程需要时再依赖 commons 模块即可:
xml
<!-- redis 处理 session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>相关配置
通过 zkui 服务直接修改配置即可
properties
/prize/application,dev=spring.session.store-type=redis
/prize/application=spring.session.store-type=redis项目相关工具框架
zkui
zkui 是 zookeeper 的一个管理工具,具有登录界面,及zk中节点和值的 crud 操作入口,支持导入导出功能。
配置文件
在【frontend】目录中的 config.cfg 文件,是 zkui 的项目配置文件
properties
#Server Port
serverPort=9090
#Comma seperated list of all the zookeeper servers
zkServer=192.168.12.132:2181
#Http path of the repository. Ignore if you dont intent to upload files from repository.
scmRepo=http://myserver.com/@rev1=
#Path appended to the repo url. Ignore if you dont intent to upload files from repository.
scmRepoPath=//appconfig.txt
#if set to true then userSet is used for authentication, else ldap authentication is used.
ldapAuth=false
ldapDomain=mycompany,mydomain
#ldap authentication url. Ignore if using file based authentication.
ldapUrl=ldap://<ldap_host>:<ldap_port>/dc=mycom,dc=com
#Specific roles for ldap authenticated users. Ignore if using file based authentication.
ldapRoleSet={"users": [{ "username":"domain\\user1" , "role": "ADMIN" }]}
userSet = {"users": [{ "username":"admin" , "password":"admin","role": "ADMIN" },{ "username":"appconfig" , "password":"appconfig","role": "USER" }]}
#Set to prod in production and dev in local. Setting to dev will clear history each time.
env=prod
jdbcClass=org.h2.Driver
jdbcUrl=jdbc:h2:zkui
jdbcUser=root
jdbcPwd=manager
#If you want to use mysql db to store history then comment the h2 db section.
#jdbcClass=com.mysql.jdbc.Driver
#jdbcUrl=jdbc:mysql://localhost:3306/zkui
#jdbcUser=root
#jdbcPwd=manager
loginMessage=Please login using admin/manager or appconfig/appconfig.
#session timeout 5 mins/300 secs.
sessionTimeout=60000
#Default 5 seconds to keep short lived zk sessions. If you have large data then the read will take more than 30 seconds so increase this accordingly.
#A bigger zkSessionTimeout means the connection will be held longer and resource consumption will be high.
zkSessionTimeout=50
#Block PWD exposure over rest call.
blockPwdOverRest=false
#ignore rest of the props below if https=false.
https=false
keystoreFile=/home/user/keystore.jks
keystorePwd=password
keystoreManagerPwd=password
# The default ACL to use for all creation of nodes. If left blank, then all nodes will be universally accessible
# Permissions are based on single character flags: c (Create), r (read), w (write), d (delete), a (admin), * (all)
# For example defaultAcl={"acls": [{"scheme":"ip", "id":"192.168.1.192", "perms":"*"}, {"scheme":"ip", id":"192.168.1.0/24", "perms":"r"}]
defaultAcl=
# Set X-Forwarded-For to true if zkui is behind a proxy
X-Forwarded-For=false启动项目
修改 config.cfg 配置文件后,可以使用以下命令来更新 zkui.jar 中的配置文件。
bash
jar uvf zkui.jar config.cfg启动
bash
java -jar zkui.jar启动后的访问地址:http://localhost:9090/
导入 zookeeper 数据
项目的 \prize-event-project\document\zk\zkui.txt 文件是微服务配置项。启动 zkui 后,使用 admin/admin登陆,通过 zkui 导入到 zookeeper 作为配置
mybatis-generator 代码生成器
mybatis-generator 项目是 mybatis 的代码生成工程
项目依赖与插件
工程的 pom 的依赖与插件配置
xml
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
<dependency>
<groupId>com.moon.prize</groupId>
<artifactId>coder</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>${project.build.directory}</outputDirectory>
<verbose>true</verbose>
<!-- 指定哪些表需要生成代码,% 代表全部 -->
<tableNames>%</tableNames>
</configuration>
</plugin>
</plugins>
</build>配置表映射关系
修改项目 resources 目录下的配置文件 generatorConfig.xml,配置数据库表与生成代码的映射关系。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 指定生成的java文件的编码,没有直接生成到项目时中文可能会乱码 -->
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 生成的pojo,将implements Serializable -->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"></plugin>
<!-- 这里的type里写的是你的实现类的类全路径 -->
<!--<commentGenerator type="com.MyCommentGenerator">
<property name="suppressDate" value="true"/>
</commentGenerator>-->
<commentGenerator>
<property name="suppressDate" value="true"/>
</commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.12.132/prize?tinyInt1isBit=false"
userId="root"
password="123456">
</jdbcConnection>
<javaTypeResolver type="com.JavaTypeResolverDefaultImpl">
</javaTypeResolver>
<!-- entity 包路径 -->
<javaModelGenerator targetPackage="com.moon.prize.commons.db.entity" targetProject="MAVEN">
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- xml 路径-->
<sqlMapGenerator targetPackage="com.moon.prize.commons.db.xml.mapper" targetProject="MAVEN"/>
<!-- dao路径 -->
<!--<javaClientGenerator type="XMLMAPPER" targetPackage="com.moon.dao1" targetProject="MAVEN"/>-->
<!--<javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.moon.dao2" targetProject="MAVEN"/>-->
<javaClientGenerator type="MIXEDMAPPER" targetPackage="com.moon.prize.commons.db.mapper" targetProject="MAVEN"/>
<table tableName="%">
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>tinyint 会映射为 bit,习惯上使用 Integer 更方便。所以定义一个 type 映射类 JavaTypeResolverDefaultImpl.java 并配置到上面的 generatorConfig.xml 中
java
public class JavaTypeResolverDefaultImpl implements JavaTypeResolver {
....
public JavaTypeResolverDefaultImpl() {
....
this.typeMap.put(-6, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TINYINT", new FullyQualifiedJavaType(Integer.class.getName())));
....
}
....
}PageHelper 分页插件
项目依赖
在聚合项目中引入依赖并进行版本控制
xml
<!-- mybatis 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>分页实体
在前台项目中的 commons 模块定义一个分页实体类:
java
@ApiModel("分页信息")
public class PageBean<T> {
@ApiModelProperty(value = "当前页,1开始")
private Integer currentPage = 1;
@ApiModelProperty(value = "每页条数,默认10")
private Integer pageSize = 10;
@ApiModelProperty(value = "总条数")
private Long totalNum;
@ApiModelProperty(value = "是否有下一页")
private Integer isMore;
@ApiModelProperty(value = "总页数")
private Integer totalPage;
@ApiModelProperty(value = "开始索引")
private Integer startIndex;
@ApiModelProperty(value = "本页数据")
private List<T> items;
public PageBean() {
super();
}
public PageBean(Integer currentPage, Integer pageSize, Long totalNum, List<T> data) {
super();
this.currentPage = currentPage;
this.pageSize = pageSize;
this.totalNum = totalNum;
this.totalPage = Math.toIntExact((this.totalNum + this.pageSize - 1) / this.pageSize);
this.startIndex = (this.currentPage - 1) * this.pageSize;
this.isMore = this.currentPage >= this.totalPage ? 0 : 1;
this.items = data;
}
....
}使用示例
在项目代码中使用:
java
// 在查询之前,调用startPage,设置页码及条数
PageHelper.startPage(curpage, limit);
List<ViewCardUserHit> all = hitMapper.selectByExample(example);查询的 sql 会被自动拼接 limit

返回的数据体:
json
{
"code": 1,
"msg": "成功",
"data": {
"currentPage": 1,
"pageSize": 2,
"totalNum": 5,
"isMore": 1,
"totalPage": 3,
"startIndex": 0,
"items": [
....
]
}
}swagger2 文档生成框架
项目依赖
在前台工程 frontend 聚合工程中定义并版本控制
xml
<!-- swagger2增强,官方ui太low , 访问地址: /doc.html -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>框架配置
在前台工程中的 api 模块创建 swagger2 的配置类(暂时只是api模块使用)
java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${spring.application.name}")
private String appName;
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
// 当前包路径
.apis(RequestHandlerSelectors.basePackage("com.moon.prize.api.action"))
.paths(PathSelectors.any()).build();
}
// 构建api文档的详细信息函数
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 页面标题
.title("抽奖系统前端" + appName)
// 创建人
.contact(new Contact("Moon", null, "moonkira@moon.com"))
// 版本号
.version("1.0")
// 描述
.description("提供给前端页面调用的相关接口")
.build();
}
}使用效果
