新增游戏排行榜统计功能

This commit is contained in:
tianfeng 2025-12-02 18:55:46 +08:00
parent 1d5ddcdf50
commit fe77a3519c
16 changed files with 621 additions and 0 deletions

View File

@ -3,6 +3,7 @@ package com.red.circle.other.adapter.app.sys;
import com.red.circle.common.business.core.ControllerRedisKeyConstant;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.framework.core.dto.AppCommand;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.framework.web.props.EnvProperties;
import com.red.circle.mq.business.model.event.game.GameSudProperties;
@ -14,8 +15,11 @@ import com.red.circle.other.app.dto.clientobject.sys.GameListConfigCO;
import com.red.circle.other.app.dto.clientobject.sys.GameMatchModelConfigGroupCO;
import com.red.circle.other.app.dto.clientobject.sys.NoticeMessageCO;
import com.red.circle.other.app.dto.clientobject.sys.SudCodeCO;
import com.red.circle.other.app.dto.clientobject.game.GameRankingCO;
import com.red.circle.other.app.dto.cmd.game.GameListQryCmd;
import com.red.circle.other.app.dto.cmd.game.GameRankingQry;
import com.red.circle.other.app.service.sys.AppConfigService;
import com.red.circle.other.app.service.game.GameRankingService;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserConsumptionLevel;
import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService;
@ -58,6 +62,7 @@ public class AppConfigRestController extends BaseController {
private final UserProfileGateway userProfileGateway;
private final EnumConfigCacheService enumConfigCacheService;
private final RedisTemplate<String,Object> redisTemplate;
private final GameRankingService gameRankingService;
/**
* 房间内-快捷入口-游戏.
@ -222,4 +227,12 @@ public class AppConfigRestController extends BaseController {
return appConfigService.listNoticeMessageLastMonth(cmd);
}
/**
* 热门游戏排行榜.
*/
@GetMapping("/game/ranking")
public PageResult<GameRankingCO> gameRanking(GameRankingQry qry) {
return gameRankingService.pageRanking(qry);
}
}

View File

@ -0,0 +1,57 @@
package com.red.circle.other.app.command.game.ranking;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.app.convertor.game.GameRankingConvertor;
import com.red.circle.other.app.dto.clientobject.game.GameRankingCO;
import com.red.circle.other.app.dto.cmd.game.GameRankingQry;
import com.red.circle.other.domain.game.GameRankingPeriodType;
import com.red.circle.other.domain.game.GameRankingRecord;
import com.red.circle.other.domain.gateway.game.GameRankingGateway;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 游戏排行榜查询执行器.
*
* @author system
*/
@Component
@RequiredArgsConstructor
public class GameRankingQryExe {
private final GameRankingGateway gameRankingGateway;
private final GameRankingConvertor gameRankingConvertor;
public PageResult<GameRankingCO> execute(GameRankingQry qry) {
String periodType = qry.getPeriodType() == null ? GameRankingPeriodType.ALL.getCode() : qry.getPeriodType();
Integer pageNum = qry.getCurrent().intValue();
Integer pageSize = qry.getSize().intValue();
// 查询排行榜数据
PageResult<GameRankingRecord> pageResult = gameRankingGateway.pageRanking(periodType, pageNum, pageSize);
// 计算排名从当前页的起始排名开始
int startRank = (pageNum - 1) * pageSize + 1;
List<GameRankingCO> coList = IntStream.range(0, pageResult.getRecords().size())
.mapToObj(i -> {
List<GameRankingRecord> records = (List<GameRankingRecord>) pageResult.getRecords();
GameRankingRecord record = records.get(i);
int rank = startRank + i;
return gameRankingConvertor.toCO(record, rank);
})
.collect(Collectors.toList());
PageResult<GameRankingCO> result = new PageResult<>();
result.setRecords(coList);
result.setTotal(pageResult.getTotal());
result.setCurrent(pageResult.getCurrent());
result.setSize(pageResult.getSize());
return result;
}
}

View File

@ -0,0 +1,34 @@
package com.red.circle.other.app.convertor.game;
import com.red.circle.other.app.dto.clientobject.game.GameRankingCO;
import com.red.circle.other.domain.game.GameRankingRecord;
import org.springframework.stereotype.Component;
/**
* 游戏排行榜转换器.
*
* @author system
*/
@Component
public class GameRankingConvertor {
/**
* 领域对象转客户端对象.
*/
public GameRankingCO toCO(GameRankingRecord record, Integer rank) {
if (record == null) {
return null;
}
GameRankingCO co = new GameRankingCO();
co.setRank(rank);
co.setGameId(record.getGameId());
co.setGameName(record.getGameName());
co.setGameCover(record.getGameCover());
co.setGameOrigin(record.getGameOrigin());
co.setSysOrigin(record.getSysOrigin());
co.setTotalPrizeAmount(record.getTotalPrizeAmount());
co.setPeriodType(record.getPeriodType());
return co;
}
}

View File

@ -217,6 +217,7 @@ public class GameBaishunServiceImpl implements GameBaishunService {
}
log.warn("{} 发送游戏获奖通知--金币数量{}", GoldOrigin.BAISHUN_GAME.name(), request.getCurrencyDiff());
return BaishunResponse.success(new BaishunChangeCurrencyResponse()
.setCurrencyBalance(receiptRes.getBalance().getDollarAmount().stripTrailingZeros()),

View File

@ -0,0 +1,32 @@
package com.red.circle.other.app.service.game;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.app.command.game.ranking.GameRankingQryExe;
import com.red.circle.other.app.dto.clientobject.game.GameRankingCO;
import com.red.circle.other.app.dto.cmd.game.GameRankingQry;
import com.red.circle.other.app.service.game.GameRankingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* 游戏排行榜服务实现.
*
* @author system
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class GameRankingServiceImpl implements GameRankingService {
private final GameRankingQryExe gameRankingQryExe;
private static final String CACHE_NAME = "game:ranking";
private static final int CACHE_TTL_MINUTES = 5;
@Override
public PageResult<GameRankingCO> pageRanking(GameRankingQry qry) {
return gameRankingQryExe.execute(qry);
}
}

View File

@ -0,0 +1,52 @@
package com.red.circle.other.app.dto.clientobject.game;
import lombok.Data;
/**
* 游戏排行榜返回对象.
*
* @author system
*/
@Data
public class GameRankingCO {
/**
* 排名
*/
private Integer rank;
/**
* 游戏ID
*/
private String gameId;
/**
* 游戏名称
*/
private String gameName;
/**
* 游戏封面
*/
private String gameCover;
/**
* 游戏源
*/
private String gameOrigin;
/**
* 归属系统
*/
private String sysOrigin;
/**
* 总中奖金额单位
*/
private Long totalPrizeAmount;
/**
* 周期类型
*/
private String periodType;
}

View File

@ -0,0 +1,20 @@
package com.red.circle.other.app.dto.cmd.game;
import com.red.circle.common.business.dto.PageQueryCmd;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 游戏排行榜查询参数.
*
* @author system
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GameRankingQry extends PageQueryCmd {
/**
* 周期类型ALL-全部WEEK-本周MONTH-本月
*/
private String periodType;
}

View File

@ -0,0 +1,21 @@
package com.red.circle.other.app.service.game;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.app.dto.clientobject.game.GameRankingCO;
import com.red.circle.other.app.dto.cmd.game.GameRankingQry;
/**
* 游戏排行榜服务.
*
* @author system
*/
public interface GameRankingService {
/**
* 分页查询游戏排行榜.
*
* @param qry 查询参数
* @return 分页结果
*/
PageResult<GameRankingCO> pageRanking(GameRankingQry qry);
}

View File

@ -0,0 +1,49 @@
package com.red.circle.other.domain.game;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 游戏排行榜累加命令.
*
* @author system
*/
@Data
@Accessors(chain=true)
public class GameRankingIncrementCmd {
/**
* 归属系统
*/
private String sysOrigin;
/**
* 游戏源
*/
private String gameOrigin;
/**
* 游戏ID
*/
private String gameId;
/**
* 游戏名称
*/
private String gameName;
/**
* 游戏封面
*/
private String gameCover;
/**
* 周期类型ALL-全部WEEK-本周MONTH-本月
*/
private String periodType;
/**
* 中奖金额单位
*/
private Long amount;
}

View File

@ -0,0 +1,47 @@
package com.red.circle.other.domain.game;
import lombok.Getter;
/**
* 游戏排行榜周期类型.
*
* @author system
*/
@Getter
public enum GameRankingPeriodType {
/**
* 全部时间
*/
ALL("ALL", "全部"),
/**
* 本周
*/
WEEK("WEEK", "本周"),
/**
* 本月
*/
MONTH("MONTH", "本月");
private final String code;
private final String name;
GameRankingPeriodType(String code, String name) {
this.code = code;
this.name = name;
}
/**
* 根据 code 获取枚举.
*/
public static GameRankingPeriodType fromCode(String code) {
for (GameRankingPeriodType type : values()) {
if (type.code.equals(code)) {
return type;
}
}
return ALL;
}
}

View File

@ -0,0 +1,52 @@
package com.red.circle.other.domain.game;
import lombok.Data;
/**
* 游戏排行榜领域对象.
*
* @author system
*/
@Data
public class GameRankingRecord {
/**
* 主键
*/
private String id;
/**
* 归属系统
*/
private String sysOrigin;
/**
* 游戏源
*/
private String gameOrigin;
/**
* 游戏ID
*/
private String gameId;
/**
* 游戏名称
*/
private String gameName;
/**
* 游戏封面
*/
private String gameCover;
/**
* 周期类型ALL-全部WEEK-本周MONTH-本月
*/
private String periodType;
/**
* 总中奖金额单位
*/
private Long totalPrizeAmount;
}

View File

@ -0,0 +1,30 @@
package com.red.circle.other.domain.gateway.game;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.domain.game.GameRankingIncrementCmd;
import com.red.circle.other.domain.game.GameRankingRecord;
/**
* 游戏排行榜网关.
*
* @author system
*/
public interface GameRankingGateway {
/**
* 分页查询游戏排行榜.
*
* @param periodType 周期类型
* @param pageNum 页码
* @param pageSize 每页大小
* @return 分页结果
*/
PageResult<GameRankingRecord> pageRanking(String periodType, Integer pageNum, Integer pageSize);
/**
* 累加中奖金额.
*
* @param cmd 累加命令
*/
void incrementPrizeAmount(GameRankingIncrementCmd cmd);
}

View File

@ -0,0 +1,81 @@
package com.red.circle.other.infra.database.mongo.entity.game.rank;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
/**
* 游戏排行榜实体.
*
* @author system
*/
@Data
@Document(collection = "game_ranking")
@CompoundIndexes({
@CompoundIndex(
name = "idx_unique_game_ranking",
def = "{'sysOrigin': 1, 'gameOrigin': 1, 'gameId': 1, 'periodType': 1}",
unique = true
),
@CompoundIndex(
name = "idx_period_amount",
def = "{'periodType': 1, 'totalPrizeAmount': -1}"
)
})
public class GameRanking {
/**
* 主键
*/
@Id
private String id;
/**
* 归属系统
*/
private String sysOrigin;
/**
* 游戏源
*/
private String gameOrigin;
/**
* 游戏ID
*/
private String gameId;
/**
* 游戏名称
*/
private String gameName;
/**
* 游戏封面
*/
private String gameCover;
/**
* 周期类型ALL-全部WEEK-本周MONTH-本月
*/
private String periodType;
/**
* 总中奖金额
*/
private Long totalPrizeAmount;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,15 @@
package com.red.circle.other.infra.database.mongo.repository.game;
import com.red.circle.other.infra.database.mongo.entity.game.rank.GameRanking;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
/**
* 游戏排行榜 Mapper.
*
* @author system
*/
@Repository
public interface GameRankingRepository extends MongoRepository<GameRanking, String> {
}

View File

@ -0,0 +1,102 @@
package com.red.circle.other.infra.gateway.game;
import com.mongodb.client.result.UpdateResult;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.domain.game.GameRankingIncrementCmd;
import com.red.circle.other.domain.game.GameRankingRecord;
import com.red.circle.other.domain.gateway.game.GameRankingGateway;
import com.red.circle.other.infra.database.mongo.entity.game.rank.GameRanking;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 游戏排行榜网关实现.
*
* @author system
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GameRankingGatewayImpl implements GameRankingGateway {
private final MongoTemplate mongoTemplate;
@Override
public PageResult<GameRankingRecord> pageRanking(String periodType, Integer pageNum, Integer pageSize) {
// 创建分页请求按中奖金额降序排序
PageRequest pageRequest = PageRequest.of(
pageNum - 1,
pageSize,
Sort.by(Sort.Direction.DESC, "totalPrizeAmount")
);
// 构建查询条件
Query query = new Query();
query.addCriteria(Criteria.where("periodType").is(periodType));
query.with(pageRequest);
// 查询总数
long total = mongoTemplate.count(Query.of(query).limit(-1).skip(-1), GameRanking.class);
// 查询数据
List<GameRanking> entities = mongoTemplate.find(query, GameRanking.class);
// 转换为领域对象
List<GameRankingRecord> records = entities.stream()
.map(this::toDomain)
.collect(Collectors.toList());
PageResult<GameRankingRecord> pageResult = new PageResult<>();
pageResult.setTotal(total);
pageResult.setCurrent(pageNum);
pageResult.setRecords(records);
pageResult.setSize(pageSize);
return pageResult;
}
@Override
public void incrementPrizeAmount(GameRankingIncrementCmd cmd) {
Query query = new Query();
query.addCriteria(Criteria.where("sysOrigin").is(cmd.getSysOrigin()))
.addCriteria(Criteria.where("gameOrigin").is(cmd.getGameOrigin()))
.addCriteria(Criteria.where("gameId").is(cmd.getGameId()))
.addCriteria(Criteria.where("periodType").is(cmd.getPeriodType()));
Update update = new Update();
update.inc("totalPrizeAmount", cmd.getAmount());
update.set("updateTime", LocalDateTime.now());
update.setOnInsert("createTime", LocalDateTime.now());
update.setOnInsert("sysOrigin", cmd.getSysOrigin());
update.setOnInsert("gameOrigin", cmd.getGameOrigin());
update.setOnInsert("gameId", cmd.getGameId());
update.setOnInsert("periodType", cmd.getPeriodType());
mongoTemplate.upsert(query, update, GameRanking.class);
}
/**
* 实体转领域对象.
*/
private GameRankingRecord toDomain(GameRanking entity) {
GameRankingRecord record = new GameRankingRecord();
record.setId(entity.getId());
record.setSysOrigin(entity.getSysOrigin());
record.setGameOrigin(entity.getGameOrigin());
record.setGameId(entity.getGameId());
record.setGameName(entity.getGameName());
record.setGameCover(entity.getGameCover());
record.setPeriodType(entity.getPeriodType());
record.setTotalPrizeAmount(entity.getTotalPrizeAmount());
return record;
}
}

View File

@ -14,7 +14,9 @@ import com.red.circle.other.app.enums.violation.GameOriginEnum;
import com.red.circle.other.app.service.activity.RankingActivityService;
import com.red.circle.other.app.service.user.user.AppUserDataViolationService;
import com.red.circle.other.app.util.OfficialNoticeUtils;
import com.red.circle.other.domain.game.GameRankingIncrementCmd;
import com.red.circle.other.domain.gateway.PropCouponGateway;
import com.red.circle.other.domain.gateway.game.GameRankingGateway;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.InviteUserGateway;
import com.red.circle.other.domain.propcoupon.PropCoupon;
@ -62,6 +64,19 @@ public class SpringTest {
private AppUserDataViolationService userDataViolationService;
@Autowired
private RankingActivityService rankingActivityService;
@Autowired
private GameRankingGateway gameRankingGateway;
@Test
public void testGameRanking() {
String sysOrigin = "LIKEI"; // 例如: "LIKEI"
String gameOrigin = "BAISHUN"; // 例如: "THIRD_PARTY" "APP"
String gameId = "1053"; // 例如: "1001"
Long prizeAmount = 500L; // 中奖金额单位
// 同时更新三个维度的排行榜
gameRankingGateway.incrementPrizeAmount(new GameRankingIncrementCmd());
}
@Test
public void testGame() {