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

news2024/11/14 13:36:16
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.coloradmin.cn/o/1904548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【技术追踪】GeCA:高分辨率医学图像合成的神经元胞扩散(MICCAI-2024)

扩散方法与传统方法相结合&#xff0c;挺有意思~ 本文提出一种称为生成式元胞自动机 (Generative Cellular Automata&#xff0c;GeCA) 的新模型系列&#xff0c;其灵感来自于生物体从单细胞进化而来的过程&#xff0c;显著提高了11 种不同眼科疾病分类任务的表现。 论文&#…

k8s 部署 springboot 项目内存持续增长问题分析解决

写在前面 工作中遇到&#xff0c;请教公司前辈解决&#xff0c;简单整理记忆博文内容涉及一次 GC 问题的分析以及解决理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99%的焦虑都来自于虚度时间和没有好好做事&#xff0c;所以唯一的解决办法就是行动起来&#xff0c;认真做完…

ES7210高性能四通道音频ADC转换模拟麦克风为IIS数字咪头

特征 高性能多位 Delta-Σ 音频 ADC 102 dB 信噪比 -85 分贝 THDN 24 位&#xff0c;8 至 100 kHz 采样频率 I2S/PCM 主串行数据端口或从串行数据端口 支持TDM 256/384Fs、USB 12/24 MHz 和其他非标准音频系统时钟 低功耗待机模式 应用 麦克风阵列 智能音箱 远场语音捕获 订购…

npm安装完yarn还是用不了?

前言 解决 找到你的包全局安装目录 复制路径&#xff0c;配置到Path全局环境变量 结果 不过发现在idea里还是用不了&#xff0c;此时你会想&#xff0c;这什么烂贴&#xff0c;没一点屁用 不过在重启idea之后&#xff0c;你也许就不会这么想了

【网络安全】实验五(身份隐藏与ARP欺骗)

一、本次实验的实验目的 &#xff08;1&#xff09;了解网络攻击中常用的身份隐藏技术&#xff0c;掌握代理服务器的配置及使用方法 &#xff08;2&#xff09;通过实现ARP欺骗攻击&#xff0c;了解黑客利用协议缺陷进行网络攻击的一般方法 二、搭配环境 打开三台虚拟机&#…

本地多卡(3090)部署通义千问Qwen2-72B大模型提速实践:从龟速到够用

最近在做文本风格转化&#xff0c;涉及千万token级别的文本。想用大模型转写&#xff0c;在线的模型一来涉及数据隐私&#xff0c;二来又不想先垫钱再找报销。本地的7-9B小模型又感觉效果有限&#xff0c;正好实验室给俺配了4卡3090的机子&#xff0c;反正也就是做个推理&#…

掌握MySQL基础命令:数据表结构修改详细操作

MySQL数据表&#xff08;Table&#xff09;是MySQL数据库中存储数据的基本结构单元。简单来说&#xff0c;数据表可以被看作是一个二维的、由行&#xff08;Row&#xff09;和列&#xff08;Column&#xff09;组成的表格&#xff0c;其中每一行代表了一个记录&#xff08;Reco…

微服务的分布式事务解决方案

微服务的分布式事务解决方案 1、分布式事务的理论模型1.1、X/Open 分布式事务模型1.2、两阶段提交协议1.3、三阶段提交协议 2、分布式事务常见解决方案2.1、TCC补偿型方案2.2、基于可靠性消息的最终一致性方案2.3、最大努力通知型方案 3、分布式事务中间件 Seata3.1、AT 模式3.…

数据跨境法案:美国篇上

近年来随着全球数字化的加速发展&#xff0c;数据已成为国家竞争力的重要基石。在这样的背景下&#xff0c;中国软件和技术出海的场景日益丰富。本系列邀请到在跨境数据方面的研究人员针对海外的数据跨境政策进行解读。 本期将针对美国对数据跨境流动的态度和政策进行阐释。过…

基础权限存储

一丶要求 建立用户组shengcan&#xff0c;其id为 2000建立用户组 caiwu&#xff0c;其id 为2001建立用户组 jishu&#xff0c;其id 为 2002建立目录/sc,此目录是 shengchan 部门的存储目录&#xff0c;只能被 shengchan 组的成员操作4.其他用户没有任何权限建立目录/cw,此目录…

两个全开源的3D模型素材下载网站源码 3D图纸模型素材 三维图形素材会员下载站源码

今天推荐两个全开源的3D模型素材下载网站源码 3D图纸模型素材 三维图形素材会员下载站源码&#xff0c;这两个源码完整&#xff0c;都是基于thinkphp内核开发的&#xff0c;框架稳定&#xff0c;带数据库&#xff0c;源码文件&#xff0c;可以直接部署使用。 第一个&#xff1a…

数据库课设---学生宿舍管理系统(sql server+C#)

1.引言 1.1 内容及要求 设计内容&#xff1a;设计学生宿舍管理系统。 设计要求&#xff1a; &#xff08;1&#xff09;数据库应用系统开发的需求分析&#xff0c;写出比较完善系统功能。 &#xff08;2&#xff09;数据库概念模型设计、逻辑模型设计以及物理模型设计。 …

【基于R语言群体遗传学】-10-适应性与正选择

在之前的博客中&#xff0c;我们学习了哈代温伯格模型&#xff0c;学习了Fisher模型&#xff0c;学习了遗传漂变与变异的模型&#xff0c;没有看过之前内容的朋友可以先看一下之前的文章&#xff1a; 群体遗传学_tRNA做科研的博客-CSDN博客 一些新名词 &#xff08;1&#xf…

AI绘画Stable Diffusion【图生图教程】:图片高清修复的三种方案详解,你一定能用上!(附资料)

大家好&#xff0c;我是画画的小强 今天给大家分享一下用AI绘画Stable Diffusion 进行 高清修复&#xff08;Hi-Res Fix&#xff09;&#xff0c;这是用于提升图像分辨率和细节的技术。在生成图像时&#xff0c;初始的低分辨率图像会通过放大算法和细节增强技术被转换为高分辨…

隔离级别-隔离级别中的锁协议、隔离级别类型、隔离级别的设置、隔离级别应用

一、引言 1、DBMS除了采用严格的两阶段封锁协议来保证并发事务的可串行化&#xff0c;实现事务的隔离性&#xff0c;也可允许用户选择一个可以保证应用程序正确执行并且能够使并发度最大的隔离性等级 2、通常用隔离级别来描述隔离性等级&#xff0c;以下将主要介绍ANSI 92标准…

【数据结构】链表带环问题分析及顺序表链表对比分析

【C语言】链表带环问题分析及顺序表链表对比分析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【C语言】链表带环问题分析及顺序表链表对比分析前言一.顺序表和链表对比1.1顺序表和链表的区别1.2缓存利用率&#…

Blender新手入门笔记收容所(一)

基础篇 基础操作 视角的控制 控制观察视角&#xff1a;鼠标中键平移视图&#xff1a;Shift鼠标中键缩放视图&#xff1a;滚动鼠标中键滚轮 选中物体后&#xff1a;移动物体快捷键G&#xff0c;移动后单击鼠标就会定下来。 进入移动状态后&#xff1a;按Y会沿着Y轴移动进入移动…

谷粒商城学习笔记-17-快速开发-逆向工程搭建使用

文章目录 一&#xff0c;克隆人人开源的逆向工程代码二&#xff0c;把逆向工程集成到谷粒商城的后台工程三&#xff0c;以商品服务为例&#xff0c;使用逆向工程生成代码1&#xff0c;修改逆向工程的配置2&#xff0c;以Debug模式启动逆向工程3&#xff0c;使用逆向工程生成代码…

机器学习Day12:特征选择与稀疏学习

1.子集搜索与评价 相关特征&#xff1a;对当前学习任务有用的特征 无关特征&#xff1a;对当前学习任务没用的特征 特征选择&#xff1a;从给定的特征集合中选择出相关特征子集的过程 为什么要特征选择&#xff1f; 1.任务中经常碰到维数灾难 2.去除不相关的特征能降低学习的…

ASCII码对照表(Matplotlib颜色对照表)

文章目录 1、简介1.1 颜色代码 2、Matplotlib库简介2.1 简介2.2 安装2.3 后端2.4 入门例子 3、Matplotlib库颜色3.1 概述3.2 颜色图的分类3.3 颜色格式表示3.4 内置颜色映射3.5 xkcd 颜色映射3.6 颜色命名表 4、Colorcet库5、颜色对照表结语 1、简介 1.1 颜色代码 颜色代码是…