利用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/780697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【5G VoNR】VoNR流程简述

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G技术研究。 博客内容主要围绕…

移动校园(5):课程表数据获取及展示

首先写下静态页面&#xff0c;起初打算做成一周的课表&#xff0c;由于是以小程序的形式展现&#xff0c;所以显示一周的话会很拥挤&#xff0c;所以放弃下面的方案&#xff0c;改作一次显示一天 改后结果如下&#xff0c;后期还会进行外观优化 真正困难的部分是数据获取 大家大…

拆分Transformer注意力,韩国团队让大模型解码提速20倍|大模型AI应用开始小规模稳步爆发|周伯文:大模型也有幻觉,全球AI创新指数公布

拆分Transformer注意力&#xff0c;韩国团队让大模型解码提速20倍AI正在颠覆AI上市不到两年&#xff0c;蜗牛游戏可能要退市了&#xff1f;世界人工智能大会结束了&#xff0c;百花齐放&#xff0c;但也群魔乱舞“串联OLED”被苹果带火了&#xff0c;比OLED强在哪里&#xff1f…

文化财经macd顶底背离幅图指标公式源码

DIFF:EMA(CLOSE,12) - EMA(CLOSE,26); DEA:EMA(DIFF,9); MACD:2*(DIFF-DEA),COLORSTICK; JC:CROSS(DIFF,DEA); SC:CROSSDOWN(DIFF,DEA); N1:BARSLAST(JC)1; N2:BARSLAST(SC)1; HH:VALUEWHEN(CROSSDOWN(DIFF,DEA),HHV(H,N1));//上次MACD红柱期间合约最大值 HH2:VALUEWHE…

MySQL:视图、用户管理、C/C++/图形化界面链接访问数据库、网页逻辑

文章目录 1.视图1.1 视图的基本使用1.2 视图的基本规则 2.用户管理2.1 创建、删除、修改用户2.2 数据库权限 3.C/C/图形化界面链接访问数据库3.1 准备工作及常用接口介绍3.2 图形化界面访问MySQL 4.用户逻辑(注册&&登录) 1.视图 视图是一个虚拟表&#xff0c;其内容由…

springboot苏桦旅游管理系统-计算机毕业设计源码02123

摘要 旅游业在全球范围内不断发展&#xff0c;为了提供高效的旅游管理和服务&#xff0c;开发一个旅游管理系统具有重要意义。本文旨在设计和实现该旅游管理系统&#xff0c;以满足用户和管理员的需求。该系统采用Spring Boot作为后端框架&#xff0c;利用其简化的开发流程和强…

ComfyUI如何高效率使用多Lora

Efficient 工作流 {"last_node_id": 29,"last_link_id": 56,"nodes": [{"id": 26,"type": "LoRA Stacker","pos": [540,270],"size": {"0": 320,"1": 322},"flag…

如何让代码兼容 Python 2 和 Python 3?Future 库助你一臂之力

目录 01Future 是什么? 为什么选择 Future? 安装与配置 02Future 的基本用法 1、兼容 print 函数 2、兼容整数除法 3、兼容 Unicode 字符串 03Future 的高级功能 1. 处理字符串与字节 2. 统一异常处理…

STM32-TIM定时器

本内容基于江协科技STM32视频内容&#xff0c;整理而得。 文章目录 1. TIM1.1 TIM定时器1.2 定时器类型1.3 基本定时器1.4 通用定时器1.4 高级定时器1.5 定时中断基本结构1.6 预分频器时序1.7 计数器时序1.8 计数器无预装时序1.9 计数器有预装时序1.10 RCC时钟树 2. TIM库函数…

路径跟踪算法之PID、PP、Stanley详细理解

一、前言 今天又来补作业了&#xff01; 在跟踪控制领域&#xff0c;PID&#xff08;Proportional-Integral-Derivative, 分别为比例、积分、微分&#xff09;、PP&#xff08; Pure-Puresuit, 纯跟踪&#xff09;、Stanley&#xff08;前轮反馈控制&#xff09;是三种最为常见…

02STM32软件安装新建工程

STM32软件安装&新建工程 1.软件安装&#xff1a;1.1Keil5 MDK安装1.2安装器件支持包离线安装支持包在线安装支持包 1.3软件注册&#xff1a;1.4安装驱动STLINK驱动JLink驱动在此文件夹下USB转串口 2开发方式&新建工程步骤&架构2.1STM32开发方式&#xff1a;库函数压…

线性系统理论及应用GUI设计及仿真

目录 1.控制系统的状态空间模型 1.1.状态空间模型 1.2 传递函数模型 1.3 传递函数转换为状态空间模型 1.4.状态空间模型转换为传递函数 1.5.状态空间模型转化为约当标准型 2.线性系统的时域分析 2.1.矩阵指数函数的计算 2.2.线型定常连续系统的状态空间模型求解 3.线…

《Nature》文章:ChatGPT帮助我学术写作的三种方式

图片翻译 ** 文章内容** 忏悔时间&#xff1a;我使用生成式人工智能&#xff08;AI&#xff09;。尽管在学术界关于聊天机器人是积极力量还是消极力量的争论不休&#xff0c;但我几乎每天都使用这些工具来完善我所写论文中的措辞&#xff0c;并寻求对我被要求评估的工作进行替…

Mysql-常用函数及其用法总结

1、字符串函数 测试用例如下&#xff1a; 1.1 CONCAT() 将多个字符串连接成一个字符串。 SELECT CONCAT(first_name, , last_name) AS full_name FROM users; -- 期望结果&#xff1a;John Doe, Jane Smith, Michael Johnson 1.2 SUBSTRING() 提取子字符串 SELECT SUBSTR…

算法012:将x减到0的最小操作数

将x减到0的最小操作数. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 这个题使用到的是滑动窗口。 乍一看&#xff0c…

纹波和噪声的介绍以及区别

纹波和噪声的介绍 纹波和噪声都是在电源输出中出现的信号波动&#xff0c;但两者存在明显的区别。   纹波&#xff1a;是附着于直流电平之上的包含周期性与随机性成分的杂波信号。在额定输出电压、电流的情况下&#xff0c;纹波指的是输出电压中的交流电压的峰值 。狭义上的纹…

生产调度:flowshop问题数学建模

接上一篇文章&#xff0c;在了解生产调度问题的背景和基本概念之后&#xff0c;我想先从比较基础的 flowshop和 jobshop 数学模型入手&#xff0c;理解实际调度过程中的问题求解思路。这一篇文章主要面向 flowshop 问题进行数学建模&#xff0c;对于这类比较经典的问题&#xf…

大语言模型基础

大语言基础 GPT : Improving Language Understanding by Generative Pre-Training 提出背景 从原始文本中有效学习的能力对于减轻自然语言处理中对监督学习的依赖至关重要。很多深度学习方法需要大量人工标注的数据&#xff0c;限制了它们在很多领域的应用&#xff0c;收集更…

【鸿蒙学习笔记】MVVM模式

官方文档&#xff1a;MVVM模式 [Q&A] 什么是MVVM ArkUI采取MVVM Model View ViewModel模式。 Model层&#xff1a;存储数据和相关逻辑的模型。View层&#xff1a;在ArkUI中通常是Component装饰组件渲染的UI。ViewModel层&#xff1a;在ArkUI中&#xff0c;ViewModel是…

【Java】垃圾回收学习笔记(二):分代假说与垃圾回收算法

文章目录 0. 分代收集理论分代假说分代GC定义 1. 垃圾回收算法1.1 标记清除&#xff08;Mark-Sweep&#xff09;算法优点缺点 1.2 标记复制算法优点缺点为什么是8:1:1&#xff1f; 1.3 标记整理算法优点缺点 2. 是否移动&#xff1f; 0. 分代收集理论 分代假说 现在多数JVM G…