Building a Spring Boot Lottery System with Redis and MyBatis‑Plus
This article demonstrates how to create a simple lottery application using Spring Boot, MyBatis‑Plus, and Redis, covering project setup, database schema, dependency configuration, Redis integration, event‑driven data initialization, lottery draw logic, stock management, and asynchronous record handling.
This tutorial walks through the implementation of a lottery system based on Spring Boot, MyBatis‑Plus, and Redis. The project caches activity, prize, and record data in Redis and performs the entire draw process using Redis operations.
1. Project Introduction
The demo is a Spring Boot application that stores activity content, prize information, and draw records in Redis, allowing the entire lottery process to be performed with Redis data operations.
2. Project Demonstration
After reviewing the UI effect, the article dives into the implementation details.
3. Database Schema
The system uses four tables: activity, prize, prize item, and draw record. The SQL statements are provided at the end of the article.
4. Project Setup
Start a standard Spring Boot project in IDEA and add the required dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
... (other dependencies omitted for brevity) ...
</dependencies>4.2 YML Configuration
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 30
max-active: 100
min-idle: 10
max-wait: 60000
redis:
port: 6379
host: 127.0.0.1
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
async:
executor:
thread:
core-pool-size: 6
max-pool-size: 12
queue-capacity: 100000
name-prefix: lottery-service-4.4 Redis Configuration
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate
redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// configure ObjectMapper ...
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}4.5 Constant Management
public class LotteryConstants {
public static final String DRAWING = "DRAWING";
public static final String LOTTERY = "LOTTERY";
public static final String LOTTERY_PRIZE = "LOTTERY_PRIZE";
public static final String DEFAULT_LOTTERY_PRIZE = "DEFAULT_LOTTERY_PRIZE";
public static final String LOTTERY_ITEM = "LOTTERY_ITEM";
public static final String DEFAULT_LOTTERY_ITEM = "DEFAULT_LOTTERY_ITEM";
public enum PrizeTypeEnum { THANK(-1), NORMAL(1), UNIQUE(2); ... }
}4.6 Business Logic
The draw API checks if the user has an unfinished draw using setIfAbsent (NX) on Redis, then proceeds with the draw, records the result, and finally clears the flag.
@GetMapping("/{id}")
public ResultResp
doDraw(@PathVariable("id") Integer id, HttpServletRequest request) {
String accountIp = CusAccessObjectUtil.getIpAddress(request);
// set NX flag for 60 seconds
Boolean result = redisTemplate.opsForValue().setIfAbsent(RedisKeyManager.getDrawingRedisKey(accountIp), "1", 60, TimeUnit.SECONDS);
if (!result) {
throw new RewardException(ReturnCodeEnum.LOTTER_DRAWING.getCode(), ReturnCodeEnum.LOTTER_DRAWING.getMsg());
}
// call service, handle exceptions, finally delete flag
redisTemplate.delete(RedisKeyManager.getDrawingRedisKey(accountIp));
return resultResp;
}The draw process loads prize data from Redis (or DB if missing), shuffles items, builds probability intervals, generates a random number, and selects the winning prize.
private LotteryItem doPlay(Lottery lottery) {
// load items from Redis or DB
// build intervals based on item.getPercent()
int luckyNumber = new Random().nextInt(mulriple);
// find matching interval and return the item
}Reward processing uses a strategy pattern. AbstractRewardProcessor defines a template method; concrete processors handle stock deduction and record insertion. Stock deduction is performed atomically via opsForHash().increment(..., -1) . If stock becomes negative, an UnRewardException triggers a fallback to a default prize.
public class HasStockRewardProcessor extends AbstractRewardProcessor {
@Override
protected void processor(RewardContext context) {
Long result = redisTemplate.opsForHash().increment(context.getKey(), "validStock", -1);
if (result.intValue() < 0) {
throw new UnRewardException(...);
}
// update DB stock and set prize info in context
}
@Override
protected void afterProcessor(RewardContext context) {
asyncLotteryRecordTask.saveLotteryRecord(context.getAccountIp(), context.getLotteryItem(), context.getPrizeName());
}
}Asynchronous record saving uses Spring's @Async with a custom thread pool defined in the YML and a ThreadPoolTaskExecutor bean.
@Async("lotteryServiceExecutor")
public void saveLotteryRecord(String accountIp, LotteryItem lotteryItem, String prizeName) {
LotteryRecord record = new LotteryRecord();
record.setAccountIp(accountIp);
record.setItemId(lotteryItem.getId());
record.setPrizeName(prizeName);
record.setCreateTime(LocalDateTime.now());
lotteryRecordMapper.insert(record);
}The article also shows an event‑driven initialization ( InitPrizeToRedisEvent and listener) that loads prize data into Redis at draw time, and an ApplicationRunner alternative for loading data on application startup.
5. Project Repository
Source code: https://gitee.com/cl1429745331/redis-demo . Adjust the activity end time in the database before running.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.