zset 可以排序 使用xxl-job实现定时任务 对历史排行榜持久化到数据库
代码
cotroller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.orchids.ranklist.web.controller;

import com.orchids.ranklist.web.domain.query.BoardQuery;
import com.orchids.ranklist.web.domain.vo.PointsBoardVO;
import com.orchids.ranklist.web.service.IPointsBoardService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @ Author qwh
* @ Date 2024/7/6 14:56
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/rank")
@Api(tags = "积分积分排行榜")
public class PointBoardController {
private final IPointsBoardService pointsBoardService;

/**
* 查询指定赛季的排行榜
* @param query
* @return
*/
@GetMapping("boards")
@ApiOperation("分页查询指定赛季的排行榜")
public PointsBoardVO queryPointsBoardBySeasonId(BoardQuery query){
return pointsBoardService.queryPointsBoardBySeasonId(query);
}
}

service

Iservice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.orchids.ranklist.web.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.orchids.ranklist.web.domain.po.PointsBoard;
import com.orchids.ranklist.web.domain.query.BoardQuery;
import com.orchids.ranklist.web.domain.vo.PointsBoardVO;

import java.util.List;

/**
* @ Author qwh
* @ Date 2024/7/6 18:55
*/

public interface IPointsBoardService extends IService<PointsBoard> {
PointsBoardVO queryPointsBoardBySeasonId(BoardQuery query);

/**
* 持久化上个月的排行榜信息之前需要创建的表
* @param season
*/
void createPointsBoardTableBySeason(Integer season);
}



servieImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package com.orchids.ranklist.web.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.orchids.ranklist.web.domain.po.PointsBoard;
import com.orchids.ranklist.web.domain.query.BoardQuery;
import com.orchids.ranklist.web.domain.vo.PointsBoardVO;
import com.orchids.ranklist.web.mapper.PointsBoardMapper;
import com.orchids.ranklist.web.service.IPointsBoardService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orchids.ranklist.web.utils.TableNameContext;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
* @author nullpointer
* @since 2024-07-06
*/
@Service
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

private final PointsBoardMapper pointsBoardMapper;

private final StringRedisTemplate redisTemplate;
// 排行榜
// 位次 id score
// 3 myID myScore
// 1 otherID otherScore
// 2 otherID otherScore
// 3 myID myScore

@Override
public PointsBoardVO queryPointsBoardBySeasonId(BoardQuery query) {
Long seasonId = query.getSeasonId();
//判断是否是当前赛季 seasonId 为 null || 0 就是当前赛季
boolean isCurrent = seasonId == null || seasonId == 0;
//是当前赛季从redis 获取每一个人的积分 积分由zset封装 userId1 score1
//拼接整个榜单的 key point:rank:board:2024:07 userId2 score2 ...
LocalDate localDate = LocalDate.now();
String format = localDate.format(DateTimeFormatter.ofPattern("yyyy:MM"));
String key = "point:rank:board:" + format;
//查询我的积分和排名
PointsBoard pointsBoard = isCurrent ?
//当前赛季查询redis
queryMyCurrentBoard(key):
//历史赛季查数据库 因为每个月初会定时把上个月的排行信息从redis中持久化到数据库中每个月的排行表中
queryMyCountHistoryBoard(seasonId);
//查询整个积分排行榜信息
List<PointsBoard> pointsBoards = isCurrent ?
queryCurrentBoardList(key,query.getPageNo(),query.getPageSize()) :
queryCountHistoryBoardList(query);
//封装排行榜信息
PointsBoardVO result = new PointsBoardVO();
if (pointsBoard!=null){
result.setRank(pointsBoard.getRank());
result.setPoints(pointsBoard.getPoints());
}
if (CollectionUtils.isEmpty(pointsBoards)){
return result;
}
//获取其他人的ID 榜单可以添加用户名信息 通过ID查询 例如
// todo List<Long> UserIds = pointsBoards.stream().map(PointsBoard::getUserId).collect(Collectors.toList());
//封装排行列表
result.setBoardList(pointsBoards);

return result;
}




/**
* 查询当前赛季积分排行榜
* @param key
* @return
*/


private PointsBoard queryMyCurrentBoard(String key) {
//当月排行信息从redis查
//获取redis操作对象
BoundZSetOperations<String, String> ops = redisTemplate.boundZSetOps(key);
//获取当前用户的 积分排行信息
//获取当前用户的UserId 可以从请求头或者token中获取 假设为
Long userId = 13666666L;
Double score = ops.score(userId.toString());
//获取我的排行信息
Long rank = ops.reverseRank(userId.toString());
//封装我的排行版信息
PointsBoard board = new PointsBoard();
board.setRank(rank.intValue()+1);
board.setUserId(userId);
board.setPoints(score.intValue());
//返回PointBoard
return board;
}

/**
* 查询我的历史赛季积分排行信息
* @param seasonId
* @return
*/
private PointsBoard queryMyCountHistoryBoard(Long seasonId) {
//todo 从数据库中查询历史排行表中的排行信息
//获取Id
//获取当前用户的UserId 可以从请求头或者token中获取 假设为
Long userId = 13666666L;
//拼接表名
TableNameContext.setInfo("points_board_"+seasonId);
//因为mybatis动态表名插件在执行查询和修改操作会 从TableNameContext中获取表名
PointsBoard board = lambdaQuery().eq(PointsBoard::getUserId, userId).one();
board.setRank(board.getId().intValue());
TableNameContext.remove();
return board;
}
/**
* 查询当前赛季积分排行榜
* @param key
* @return
*/
private List<PointsBoard> queryCurrentBoardList(String key, Integer pageNo, Integer pageSize) {
//计算分页信息
int from = (pageNo - 1) * pageSize;
int end = from + pageSize + 1;
//从redis中查询
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, from, end);
//判断是否为空
if (CollectionUtils.isEmpty(tuples)) {
return new ArrayList<>();
}
//封装排行榜信息
int rank = from + 1;
List<PointsBoard> result = new LinkedList<>();
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
String userId = tuple.getValue(); //用户Id
Double score = tuple.getScore(); //用户积分
if (userId==null||score==null){
continue;
}
PointsBoard board = new PointsBoard();
board.setRank(rank++);
board.setUserId(Long.valueOf(userId));
board.setPoints(score.intValue());
result.add(board);
}
return result;
}

/**
* 查询历史赛季积分排行榜
* @param query
* @return
*/
private List<PointsBoard> queryCountHistoryBoardList(BoardQuery query) {
//todo 后序查询数据库
//获取赛季Id
Long seasonId = query.getSeasonId();
//拼接查询的表
TableNameContext.setInfo("points_board_"+seasonId);
Page<PointsBoard> ipage = new Page<>(query.getPageNo(), query.getPageSize());
Page<PointsBoard> page = pointsBoardMapper.selectPage(ipage, null);
List<PointsBoard> boardList = page.getRecords();
//这里可以 获取用户ID 查询用户信息 修改显示内容
TableNameContext.remove();
return boardList;
}

@Override
public void createPointsBoardTableBySeason(Integer season) {
// 第七赛季的排行榜 表实例 points_board_7
pointsBoardMapper.createPointsBoardTable("points_board_" + season);
}
}

定时任务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.orchids.ranklist.web.handler;

/**
* @ Author qwh
* @ Date 2024/7/6 20:41
*/
import com.orchids.ranklist.web.domain.po.PointsBoard;
import com.orchids.ranklist.web.service.IPointsBoardSeasonService;
import com.orchids.ranklist.web.service.IPointsBoardService;
import com.orchids.ranklist.web.utils.TableNameContext;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

//定时任务 每月初定时创建榜单表 将redis中的数据持久化到数据库
@Slf4j
@Component
@RequiredArgsConstructor
public class PointsBoardPersistentHandler {
//查询赛季信息
private final IPointsBoardSeasonService seasonService;
//创建赛季表
private final IPointsBoardService pointsBoardService;
//删除redis中的上个月榜单
private final StringRedisTemplate redisTemplate;


@XxlJob("xxl_job_time_test")
public void createLocalTime(){
log.debug("xl_job_demo执行器正在执行现在时间是{}",LocalDateTime.now());
System.out.println("xl_job_demo执行器正在执行现在时间是"+LocalDateTime.now());
}
// 每月1号,凌晨3点执行
//todo 添加定时或者 XXL_JOB
@XxlJob("xxl_job_points_board_create_table")
public void createPointBoardTableOfSeason(){
//上个月的凌晨三点
LocalDateTime time = LocalDateTime.now().minusMonths(1);
//查询赛季表获取赛季Id
Integer season = seasonService.querySeasonByTime(time);
if (season == null){
return;
}
//将表名保存到ThreadLocal
TableNameContext.setInfo("points_board_" + season);
//创建对应的表
pointsBoardService.createPointsBoardTableBySeason(season);
}

/**
* 持久化排行榜到数据库
*/
//todo 添加定时或者 XXL_JOB
@XxlJob("xxl_job_points_board_save_mysql")
private void savePointsBoardToDb() {
//拼接redis key
LocalDateTime time = LocalDateTime.now().minusMonths(1);
//计算动态表名
Integer season = seasonService.querySeasonByTime(time);
TableNameContext.setInfo("points_board_"+season);
//拼接key
LocalDateTime now = LocalDateTime.now();
String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM"));
// point:rank:board:2024:06
String key = "point:rank:board:" + format;
//获取redis中的数据
// todo 可以利用分片xxl_job
int pageNo =1;
int pageSize = 1000;
System.out.println(key);

while (true){
List<PointsBoard> pointsBoards = queryCurrentBoardList(key, pageNo, pageSize);
if (CollectionUtils.isEmpty(pointsBoards)){
//当没有数据了跳过循环
break;
}
//持久化到数据库
pointsBoardService.saveBatch(pointsBoards);
System.out.println("持久化成功");
pageNo++;
}
//任务结束 移除表名
TableNameContext.remove();
}
//删除redis中的数据
//todo 添加定时或者 XXL_JOB
@XxlJob("xxl_job_points_board_remove_redis")
public void clearPointsBoardFromRedis(){
//获取上个月的时间
LocalDateTime time = LocalDateTime.now().minusMonths(1);
//拼接key
String key = "point:rank:board:" + time.format(DateTimeFormatter.ofPattern("yyyy:MM"));
//删除上个月的redis缓存数据
redisTemplate.unlink(key);
}
public List<PointsBoard> queryCurrentBoardList(String key, Integer pageNo, Integer pageSize) {
//计算分页信息
int from = (pageNo - 1) * pageSize;
int end = from + pageSize + 1;
//从redis中查询
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, from, end);
//判断是否为空
if (CollectionUtils.isEmpty(tuples)) {
return new ArrayList<>();
}
//封装排行榜信息
int rank = from + 1;
List<PointsBoard> result = new LinkedList<>();
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
String userId = tuple.getValue(); //用户Id
Double score = tuple.getScore(); //用户积分
if (userId==null||score==null){
continue;
}
PointsBoard board = new PointsBoard();
board.setId(Long.valueOf(rank++));
board.setUserId(Long.valueOf(userId));
board.setPoints(score.intValue());
result.add(board);
}
return result;
}


}

配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.orchids.ranklist.web.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.orchids.ranklist.web.utils.TableNameContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;


/**
* @ Author qwh
* @ Date 2024/7/6 14:49
*/
@Configuration
public class MybatisPlusConfiguration {
/**
* 添加动态表明插件
*/
@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
// 准备一个Map,用于存储TableNameHandler
Map<String, TableNameHandler> map = new HashMap<>(1);
// 存入一个TableNameHandler,用来替换points_board表名称
// 替换方式,就是从TableInfoContext中读取保存好的动态表名
map.put("points_board", (sql, tableName) -> TableNameContext.getInfo() == null ? tableName : TableNameContext.getInfo());
return new DynamicTableNameInnerInterceptor(map);
}
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(@Autowired(required = false) DynamicTableNameInnerInterceptor nameInnerInterceptor) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//表名替换插件 //todo !!!!!!!!!!!!!!!!!!!!!注意注意(っ °Д °;)っ
interceptor.addInnerInterceptor(nameInnerInterceptor);
//f分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
}


xxl配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.orchids.ranklist.web.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @ Author qwh
* @ Date 2024/7/7 9:28
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobConfig {
/**
* 配置XxlJobSpringExecutor Bean,用于初始化XxlJob的执行器。
* @param prop XxlJobProperties实例,包含XxlJob的配置信息。
* @return 初始化后的XxlJobSpringExecutor实例。
*/
@Bean
public XxlJobSpringExecutor xxlJobExecutor(XxlJobProperties prop) {
// 初始化日志,表示XxlJob配置开始初始化
log.info(">>>>>>>>>>> xxl-job config init.");

// 创建XxlJobSpringExecutor实例
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();

// 配置管理员地址
XxlJobProperties.Admin admin = prop.getAdmin();
if (admin != null && admin.getAddress()!=null) {
xxlJobSpringExecutor.setAdminAddresses(admin.getAddress());
}

// 配置执行器信息
XxlJobProperties.Executor executor = prop.getExecutor();
if (executor != null) {
// 配置执行器名称
if (executor.getAppName() != null)
xxlJobSpringExecutor.setAppname(executor.getAppName());
// 配置执行器IP
if (executor.getIp() != null)
xxlJobSpringExecutor.setIp(executor.getIp());
// 配置执行器端口
if (executor.getPort() != null)
xxlJobSpringExecutor.setPort(executor.getPort());
// 配置日志路径
if (executor.getLogPath() != null)
xxlJobSpringExecutor.setLogPath(executor.getLogPath());
// 配置日志保留天数
if (executor.getLogRetentionDays() != null)
xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
}

// 配置访问令牌
if (prop.getAccessToken() != null)
xxlJobSpringExecutor.setAccessToken(prop.getAccessToken());

// 初始化日志,表示XxlJob配置结束
log.info(">>>>>>>>>>> xxl-job config end.");

// 返回初始化后的执行器实例
return xxlJobSpringExecutor;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.orchids.ranklist.web.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @ Author qwh
* @ Date 2024/7/7 9:28
*/
@Data
//排除没有加入xxl_job的服务避免无法生成bean导致启动失败
@ConditionalOnClass(XxlJobSpringExecutor.class)
@ConfigurationProperties(prefix = "xxl-job")
public class XxlJobProperties {
private String accessToken;
private Admin admin;
private Executor executor;

@Data
public static class Admin {
private String address;
}

@Data
public static class Executor {
private String appName;
private String address;
private String ip;
private Integer port;
private String logPath;
private Integer logRetentionDays;

}
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--接口的地址com开始到接口名UserMapper-->
<mapper namespace="com.orchids.ranklist.web.mapper.PointsBoardMapper">

<!--sql语句-->
<insert id="createPointsBoardTable">
CREATE TABLE `${tableName}`
(
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '榜单id',
`user_id` BIGINT NOT NULL COMMENT '学生id',
`points` INT NOT NULL COMMENT '积分值',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id` (`user_id`) USING BTREE
)
COMMENT ='学霸天梯榜'
COLLATE = 'utf8mb4_0900_ai_ci'
ENGINE = InnoDB
ROW_FORMAT = DYNAMIC
</insert>
</mapper>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 应用服务 WEB 访问端口
server:
port: 8081
spring:
application:
name: rank-list
# knife4j 额外配置
mvc:
pathmatch:
matching-strategy: ant_path_matcher
# Redis 配置
redis:
port: 6379
host: localhost
password: 6379
# 数据库 配置
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/tianji_redis?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8
username: root
password: 123123
hikari:
connection-test-query: SELECT 1 # 自动检测连接
connection-timeout: 60000 #数据库连接超时时间,默认30秒
idle-timeout: 500000 #空闲连接存活最大时间,默认600000(10分钟)
max-lifetime: 540000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
maximum-pool-size: 12 #连接池最大连接数,默认是10
minimum-idle: 10 #最小空闲连接数量
pool-name: SPHHikariPool # 连接池名称
# Mybatis-plus 配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
xxl-job:
# 访问令牌 不能乱改 要和jar包中的一致
access-token: default_token
admin:
address: http://localhost:8080/xxl-job-admin
executor:
appname: rank-list
# 日志保存时间
log-retention-days: 10
# 日志地址
logPath: rank-list
#自动获取
# port: 9999
#ip会自动获取
# ip: localhost



其他的类

PointsBoard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.orchids.ranklist.web.domain.po;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
* <p>
* 学霸天梯榜
* </p>
*
* @author nullpointer
* @since 2024-07-06
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
//@TableName("points_board") //每月初 持久化一个赛季的排行信息 到数据库 表为 points_board_seasonId
public class PointsBoard implements Serializable {

private static final long serialVersionUID = 1L;

/**
* 榜单id
*/
@TableId(value = "id")
private Long id;

/**
* 学生id
*/
@TableField("user_id")
private Long userId;

/**
* 积分值
*/
@TableField("points")
private Integer points;

/**
* 名次,只记录赛季前100
*/
@TableField(exist = false)
private Integer rank;

/**
* 赛季,例如 1,就是第一赛季,2-就是第二赛季
*/
@TableField(exist = false)
private Integer season;


}


PointsBoardSeason
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.orchids.ranklist.web.domain.po;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDate;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
* <p>
*
* </p>
*
* @author nullpointer
* @since 2024-07-06
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("points_board_season")
public class PointsBoardSeason implements Serializable {

private static final long serialVersionUID = 1L;

/**
* 自增长id,season标示
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;

/**
* 赛季名称,例如:第1赛季
*/
private String name;

/**
* 赛季开始时间
*/
private LocalDate beginTime;

/**
* 赛季结束时间
*/
private LocalDate endTime;


}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.orchids.ranklist.web.domain.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.Min;

/**
* @ Author qwh
* @ Date 2024/7/6 19:53
*/
@Data
@ApiModel(description = "分页请求参数")
@Accessors(chain = true)
public class BoardQuery {
public static final Integer DEFAULT_PAGE_SIZE = 20;
public static final Integer DEFAULT_PAGE_NUM =1;
@ApiModelProperty(value = "页码", example = "1")
@Min(value = 1, message = "页码不能小于1")
private Integer pageNo = DEFAULT_PAGE_NUM;

@ApiModelProperty(value = "每页大小", example = "5")
@Min(value = 1, message = "每页查询数量不能小于1")
private Integer pageSize = DEFAULT_PAGE_SIZE;

@ApiModelProperty(value = "赛季id,为null或者0则代表查询当前赛季")
private Long seasonId;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.orchids.ranklist.web.domain.vo;

import com.orchids.ranklist.web.domain.po.PointsBoard;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

/**
* @ Author qwh
* @ Date 2024/7/6 19:01
*/
@Data
@ApiModel(description = "积分榜单汇总信息")
public class PointsBoardVO {
@ApiModelProperty("我的榜单排名")
private Integer rank;
@ApiModelProperty("我的积分值")
private Integer points;
@ApiModelProperty("前100名上榜人信息")
private List<PointsBoard> boardList;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.orchids</groupId>
<artifactId>rank-list</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rank-list</name>
<description>rank-list</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.orchids.ranklist.RankListApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

测试

当前赛季
image.png
image.png
历史赛季
image.png
image.png