利用redis Zset实现 排行榜功能 配合xxl-job持久化每一个赛季的排行榜

zset 可以排序 使用xxl-job实现定时任务 对历史排行榜持久化到数据库
排行榜有当前赛季排行版和历史排行榜

当前赛季排行榜利用redis 中的SortSet 数据结构 获取
每个月的 月初 利用xxl-job的定时任务持久化化上一个月的排行榜信息 并删除redis中的数据
当排行榜数据量巨大时可以 通过对每一个赛季的历史排行榜水平分表 减小单表的数据量和压力
查询历史排行榜 通过持久化的表查询

代码
cotroller
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

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

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);
    }
}

定时任务类
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;
    }


}

配置类
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配置类
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;
    }
}
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;

    }
}



<?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>

# 应用服务 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

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

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;


}

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;
}

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;
}

<?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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值