通化市建设工程招投标网站佛山专业做网站
2025/12/31 16:53:39 网站建设 项目流程
通化市建设工程招投标网站,佛山专业做网站,163企业邮箱登录入口官网,深圳龙华属于哪个区接口 一 实时排行榜 1.查询赛季列表功能参数说明请求方式GET请求路径/boards/seasons/list请求参数无返回值[ { id: 110, // 赛季id name: 第一赛季, // 赛季名称 beginTime: 2023-05-01, …接口一 实时排行榜1.查询赛季列表功能参数说明请求方式GET请求路径/boards/seasons/list请求参数无返回值[ { id: 110, // 赛季id name: 第一赛季, // 赛季名称 beginTime: 2023-05-01, // 赛季开始时间 endTime: 2023-05-31, // 赛季结束时间 } ]PointsBoardSeasonController.java/** * 查询赛季列表 * return */ApiOperation(查询赛季列表)GetMapping(/list)publicListPointsBoardSeasonlist(){returnpointsBoardSeasonService.list();}2.实时排行榜功能基于Zset改造之前的代码RedisConstants.java/** * 积分排行榜的前缀 boards:202501 */StringPOINTS_BOARDS_KEY_PREFIXboards:;PointsRecordServiceImpl.javaOverridepublicvoidaddPointsRecord(LonguserId,intpoint,PointsRecordTypetype){//判断该积分类型是否有上限 type.maxPoints是否大于0if(point0){return;}intmaxPointstype.getMaxPoints();LocalDateTimenowLocalDateTime.now();if(maxPoints0){LocalDateTimedayStartTimeDateUtils.getDayStartTime(now);LocalDateTimedayEndTimeDateUtils.getDayEndTime(now);//如果有上限 查询该用户 该积分类型 今日已得积分 points_record 条件userId typeQueryWrapperPointsRecordwrappernewQueryWrapper();wrapper.select(sum(points) as totalPoints);wrapper.eq(user_id,userId);wrapper.eq(type,type);wrapper.between(create_time,dayStartTime,dayEndTime);MapString,Objectmapthis.getMap(wrapper);//当前用户该积分类型 已得积分intcurrentPoints0;if(map!nullmap.containsKey(totalPoints)){BigDecimaltotalPoints(BigDecimal)map.get(totalPoints);currentPointstotalPoints.intValue();}//判断已得积分是否超过上限if(currentPointsmaxPoints){//说明已得积分 达到上限return;}// 此时的point标识能得得积分if(currentPointspointmaxPoints){pointmaxPoints-currentPoints;}}//保存积分PointsRecordrecordnewPointsRecord();record.setUserId(userId);record.setType(type);record.setPoints(point);this.save(record);// 累计积分添加到Redis当中改造部分StringkeyRedisConstants.POINTS_BOARDS_KEY_PREFIXnow.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);stringRedisTemplate.opsForZSet().incrementScore(key,userId.toString(),point);}3.查询学霸积分排行榜接口说明查询指定赛季的积分排行榜以及当前用户的积分和排名信息请求方式GET请求路径/boards请求参数分页参数例如PageNo、PageSize赛季id为空或0时代表查询当前赛季。否则就是查询历史赛季返回值{ rank: 8, // 当前用户的排名 points: 21, // 当前用户的积分值 [ { rank: 1, // 排名 points: 81, // 积分值 name: Jack // 姓名 } { rank: 2, // 排名 points: 74, // 积分值 name: Rose // 姓名 } ] }PointsBoardController.javaApiOperation(查询学霸积分榜-当前赛季和历史赛季都可用)GetMappingpublicPointsBoardVOqueryPointsBoardList(PointsBoardQueryquery){returnpointsBoardService.queryPointsBoardList(query);}IPointsBoardService.javaPointsBoardVOqueryPointsBoardList(PointsBoardQueryquery);PointsBoardServiceImpl.javaOverridepublicPointsBoardVOqueryPointsBoardList(PointsBoardQueryquery){// 获取当前登录用户idLonguserIdUserContext.getUser();// 判断是查当前赛季还是历史赛季 query.seasonbooleanisCurrentquery.getSeason()null||query.getSeason()0;LocalDatenowLocalDate.now();Stringformatnow.format(DateTimeFormatter.ofPattern(yyyyMM));StringkeyRedisConstants.POINTS_BOARD_KEY_PREFIXformat;Longseasonquery.getSeason();// 查询我的排名和积分PointsBoardboardisCurrent?queryMyCurrentBoard(key):queryMyHistoryBoard(season);// 分页查询赛季列表ListPointsBoardlistisCurrent?queryCurrentBoard(key,query.getPageNo(),query.getPageSize()):queryHistoryBoard(query);// 封装vo返回PointsBoardVOvonewPointsBoardVO();vo.setRank(board.getRank());//我的排名vo.setPoints(board.getPoints());//我的积分//封装用户id集合 调用用户服务 获取用户信息 转mapSetLonguidslist.stream().map(PointsBoard::getUserId).collect(Collectors.toSet());ListUserDTOuserDTOSuserClient.queryUserByIds(uids);if(userDTOS.isEmpty()){thrownewBizIllegalException(用户不存在);}//转map key:用户id value 用户名称MapLong,StringuserDtoMapuserDTOS.stream().collect(Collectors.toMap(UserDTO::getId,c-c.getName()));ListPointsBoardItemVOvoListnewArrayList();for(PointsBoardpointsBoard:list){PointsBoardItemVOitemVOnewPointsBoardItemVO();itemVO.setName(userDtoMap.get(pointsBoard.getUserId()));itemVO.setPoints(pointsBoard.getPoints());itemVO.setRank(pointsBoard.getRank());voList.add(itemVO);}vo.setBoardList(voList);returnvo;}/** * 查询历史赛季排行榜列表 * * param query * return */privateListPointsBoardqueryHistoryBoard(PointsBoardQueryquery){if(query.getPageNo()0||query.getPageSize()0){thrownewBadRequestException(非法参数);}intoffsetquery.getPageNo()-1;ListPointsBoardlistthis.lambdaQuery().eq(PointsBoard::getSeason,query.getSeason()).orderByAsc(PointsBoard::getPoints).last(LIMIT query.getPageSize() OFFSET offset).list();returnlist;}/** * 查询当前赛季排行榜列表 * * param key * param pageNo 页码 * param pageSize 条数 * return */privateListPointsBoardqueryCurrentBoard(Stringkey,IntegerpageNo,IntegerpageSize){// 计算start和stop 下标都是从零开始intstart(pageNo-1)*pageSize;intendstartpageSize-1;// 利用zrevrange 按分数倒序 分页查询SetZSetOperations.TypedTupleStringtypedTuplesredisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,start,end);if(CollUtils.isEmpty(typedTuples)){returnCollUtils.emptyList();}intrankstart1;ListPointsBoardlistnewArrayList();//封装结果返回for(ZSetOperations.TypedTupleStringtypedTuple:typedTuples){StringvaluetypedTuple.getValue();//用户idDoublescoretypedTuple.getScore();//总积分值if(StringUtils.isBlank(value)||scorenull){continue;}PointsBoardboardnewPointsBoard();board.setUserId(Long.valueOf(value));board.setPoints(score.intValue());board.setRank(rank);list.add(board);}returnlist;}/** * 查询历史赛季我的积分和排名 * * param season * return */privatePointsBoardqueryMyHistoryBoard(Longseason){LonguserIdUserContext.getUser();if(seasonnull){thrownewBadRequestException(非法参数);}PointsBoardonethis.lambdaQuery().eq(PointsBoard::getSeason,season).eq(PointsBoard::getUserId,userId).one();returnone;}/** * 查询当前赛季我的积分和排名 * * param key * return */privatePointsBoardqueryMyCurrentBoard(Stringkey){// 获取当前登录用户idLonguserIdUserContext.getUser();// 从Redis中获取分值DoublescoreredisTemplate.opsForZSet().score(key,userId.toString());// 获取排名 从0开始 需要加一LongrankredisTemplate.opsForZSet().reverseRank(key,userId.toString());PointsBoardboardnewPointsBoard();board.setRank(ranknull?0:rank.intValue()1);board.setPoints(scorenull?0:score.intValue());returnboard;}二 历史排行榜1.定时任务生成榜单表PointsBoardPersistentHandler.javaXxlJob(createTableJob)publicvoidcreatePointBoardTableOfLastSeason(){//1.获取上个月的时间LocalDateTimetimeLocalDateTime.now().minusMonths(1);//2.查询赛季idIntegerseasonIdpointsBoardSeasonService.querySeasonIdByTime(time);//3.创建积分榜单表if(seasonIdnull){thrownewBadRequestException(当前赛季不存在);}pointsBoardService.createPointsBoardTable(seasonId);}IPointsBoardSeasonService.javaIPointsBoardService.javaIntegerquerySeasonIdByTime(LocalDateTimetime);voidcreatePointsBoardTable(IntegerseasonId);PointsBoardSeasonServiceImpl.javaPointsBoardServiceImpl.javaOverridepublicIntegerquerySeasonIdByTime(LocalDateTimetime){OptionalPointsBoardSeasonpointsBoardSeasonlambdaQuery().le(PointsBoardSeason::getBeginTime,time).ge(PointsBoardSeason::getEndTime,time).oneOpt();returnpointsBoardSeason.map(PointsBoardSeason::getId).orElse(null);}Override public void createPointsBoardTable(Integer seasonId) { String tableName points_board_ seasonId; mapper.createPointsBoardTable(tableName); }PointsBoardMappervoid createPointsBoardTable(Param(tableName) String seasonId);PointsBoardMapper.xmlinsertidcreatePointsBoardTableCREATE TABLE if not exists ${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/insert2.定时任务榜单持久化TableInfoContext创建一个ThreadLocal工具类在工作线程当中去存储表名package com.tianji.learning.utils; /** * 获取当前线程的TableInfo对象 * * author ax */ public class TableInfoContext { private static final ThreadLocalString TABLE_INFO new ThreadLocal(); public static void setTableInfo(String tableInfo) { TABLE_INFO.set(tableInfo); } public static String getTableInfo() { return TABLE_INFO.get(); } public static void remove() { TABLE_INFO.remove(); } }MybatisConfig.java声明对应的配置类去实现对表名的修改通过拦截器机制和线程上下文传递优雅地实现了逻辑表名到物理表名的动态映射。MybatisConfig搭建了处理流水线MybatisConfiguration为特定表配置了换名规则而具体的表名信息则由业务代码通过TableInfoContext在关键时刻传递。整个过程对业务代码入侵极小是分库分表场景下的经典解决方案。MybatisConfiguration.javapackage com.tianji.learning.config; import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler; import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor; import com.tianji.learning.utils.TableInfoContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * mybatis配置类 * * author ax */ Configuration public class MybatisConfiguration { Bean public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() { MapString, TableNameHandler map new HashMap(1); map.put(points_board, (sql, tableName) - TableInfoContext.getTableInfo()); return new DynamicTableNameInnerInterceptor(map); } }Mybatis-plus的配置类packagecom.tianji.common.autoconfigure.mybatis;importcom.baomidou.mybatisplus.annotation.DbType;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;importcom.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;importcom.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.autoconfigure.condition.ConditionalOnClass;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationConditionalOnClass({MybatisPlusInterceptor.class,BaseMapper.class})publicclassMybatisConfig{/** * see MyBatisAutoFillInterceptor 通过自定义拦截器来实现自动注入creater和updater * deprecated 存在任务更新数据导致updater写入0或null的问题暂时废弃 */// Bean// ConditionalOnMissingBeanpublicBaseMetaObjectHandlerbaseMetaObjectHandler(){returnnewBaseMetaObjectHandler();}BeanConditionalOnMissingBeanpublicMybatisPlusInterceptormybatisPlusInterceptor(Autowired(requiredfalse)DynamicTableNameInnerInterceptordynamicTableNameInnerInterceptor){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();PaginationInnerInterceptorpaginationInnerInterceptornewPaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(200L);interceptor.addInnerInterceptor(paginationInnerInterceptor);interceptor.addInnerInterceptor(newMyBatisAutoFillInterceptor());if(dynamicTableNameInnerInterceptor!null){interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);}returninterceptor;}}PointsBoardPersistentHandler.java这里还借助了xxljob的分片广播主要应用于多实例部署时可以实现多实例分片类似于取模运算的思路每个实例负责不同的部分。XxlJob(savePointsBoard2DB)publicvoidsavePointsBoard2DB(){//1.获取上个月的时间LocalDateTimetimeLocalDateTime.now().minusMonths(1);//2.查询赛季id// 2.1拼接Redis当中的keyStringkeyRedisConstants.POINTS_BOARDS_KEY_PREFIXtime.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);// 2.2查询数据PointsBoardQuerypointsBoardQuerynewPointsBoardQuery();intindexXxlJobHelper.getShardIndex();inttotalXxlJobHelper.getShardTotal();pointsBoardQuery.setPageNo(index1);pointsBoardQuery.setPageSize(100);// 2.3计算动态表名存入ThreadLocal中IntegerseasonIdpointsBoardSeasonService.querySeasonIdByTime(time);TableInfoContext.setTableInfo(points_board_seasonId);while(true){ListPointsBoardpointsBoardspointsBoardService.queryCurrentBoardList(key,pointsBoardQuery);if(CollUtils.isEmpty(pointsBoards)){break;}// 持久化到数据库mapper.saveDb(pointsBoards,TableInfoContext.getTableInfo());//翻页pointsBoardQuery.setPageNo(pointsBoardQuery.getPageNo()total);}TableInfoContext.remove();}queryCurrentBoardList方法Override public ListPointsBoard queryCurrentBoardList(String key, PointsBoardQuery query) { Integer pageNo query.getPageNo(); Integer pageSize query.getPageSize(); int from (pageNo - 1) * pageSize; SetZSetOperations.TypedTupleString typedTuples redisTemplate.opsForZSet().reverseRangeWithScores(key, from, from pageSize - 1); int rank from 1; if (CollUtils.isEmpty(typedTuples)) { return CollUtils.emptyList(); } ListPointsBoard list new ArrayList(typedTuples.size()); for (ZSetOperations.TypedTupleString tuple : typedTuples) { String value tuple.getValue(); Double score tuple.getScore(); if (score null || value null) { continue; } PointsBoard board new PointsBoard(); board.setUserId(Long.valueOf(value)); board.setPoints(score.intValue()); board.setRank(rank); list.add(board); } return list; }PointsBoardMapper.javavoid saveDb(Param(list) ListPointsBoard pointsBoards, Param(tableName) String tableName);PointsBoardMapper.xml这里是将五个字段修改为三个字段的映射rank排名映射为iduser_id与points则是普通的映射。insert idsaveDb insert into ${tableName} (id, user_id, points) values foreach collectionlist itemitem separator, (#{item.rank}, #{item.userId}, #{item.points}) /foreach /insertRedis中del和unlink区别在Redis中DEL和UNLINK命令都用于删除键但它们在实现机制和对系统性能的影响上有显著区别1.同步 vs 异步DEL命令同步删除。直接删除键及其关联的数据操作会立即释放内存。如果删除的键对应大型数据结构如包含数百万元素的哈希或列表DEL可能会阻塞主线程导致其他请求延迟。UNLINK命令异步删除。首先将键从键空间keyspace中移除逻辑删除后续的内存回收由后台线程处理。命令立即返回不会阻塞主线程适合删除大对象。2.性能影响DEL删除大键时可能引发明显延迟影响Redis的响应时间。UNLINK几乎无阻塞适合高吞吐场景尤其适用于需要频繁删除大键的情况。3.使用场景DEL适合删除小键或对内存释放时效性要求高的场景如避免内存不足。UNLINK推荐在大多数情况下使用尤其是删除大键或需要低延迟的场景。4.返回值两者均返回被删除键的数量但UNLINK返回时数据可能尚未完全释放。5.版本要求UNLINK自Redis 4.0引入需确保版本支持DEL在所有版本中可用。示例对比# 同步删除可能阻塞主线程DEL large_key# 异步删除立即返回后台清理UNLINK large_key总结特性DELUNLINK删除方式同步异步阻塞主线程是大键时否适用场景小键或需立即释放内存大键或高并发场景版本支持所有版本Redis 4.0建议优先

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询