增加离线排行榜结算模块

grimm 2025-04-16 21:05:36 +08:00
parent e647df75bd
commit 0690a68dee
11 changed files with 423 additions and 39 deletions

View File

@ -1194,6 +1194,12 @@ public class TimeUtils {
return calendar.getTimeInMillis();
}
public static int getMinute() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
return calendar.get(Calendar.MINUTE);
}
/**
* 现在时间是否超过当天指定时间
* hour:24

View File

@ -15,6 +15,7 @@ import com.ljsd.jieling.db.redis.RedisUtil;
import com.ljsd.jieling.ktbeans.ReportUtil;
import com.ljsd.jieling.logic.dao.ServerConfig;
import com.ljsd.jieling.logic.dao.root.GlobalSystemControl;
import com.ljsd.jieling.logic.rankReward.RankRewardLogic;
import com.ljsd.jieling.network.NetManager;
import com.ljsd.jieling.util.ConfigurableApplicationContextManager;
import com.ljsd.worldmanager.WorldServerListListener;
@ -88,6 +89,8 @@ public class GameApplication {
GameLogicService.getInstance().onStart();
//每隔五分钟重载一次工会缓存
GsGuildInfoCacheLoader.loadGuildInfoCache();
//初始化榜单结算信息
RankRewardLogic.getInstance().init();
//监听世界服列表
new WorldServerListListener().start();
new ReportUtil().start();

View File

@ -470,6 +470,10 @@ public class RedisKey {
public static Set<String> rankCacChe = new HashSet<>();
public static final String AREAN_DAY_RANK_MARK = "AREAN_DAY_RANK_MARK";//竞技场每日奖励排行标记信息
public static final String AREAN_SEASON_RANK_MARK = "AREAN_SEASON_RANK_MARK";//竞技场赛季奖励排行标记信息
//public static Set<String> worldDeathPathCacChe = new HashSet<>();
static {

View File

@ -1,6 +1,7 @@
package com.ljsd.jieling.db.redis;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ljsd.GameApplication;
import com.ljsd.common.mogodb.util.GlobalData;
import com.ljsd.jieling.db.mongo.AreaManager;
@ -1300,6 +1301,18 @@ public class RedisUtil {
}
}
public <T> void putMap(String key, String mapKey, T value) {
for (int i = 0; i < MAX_TRY_TIMES; i++) {
try {
redisTemplate.opsForHash().put(key, mapKey, gson.toJson(value));
return;
} catch (Exception e) {
LOGGER.info("redis {}",e);
sleep(FAILED_SLEEP);
}
}
}
public <T> void putMapEntrys(String type, String key, Map<String, T> valus) {
String rkey = getKey(type, key);
Map<String, String> result = new HashMap<>();
@ -1375,6 +1388,17 @@ public class RedisUtil {
}
return result;
}
public Map<Integer,Map<Integer,Integer>> getMapValues(String key){
Map<Integer,Map<Integer,Integer>> result = new HashMap<Integer,Map<Integer,Integer>>();
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
Type type = new TypeToken<HashMap<Integer, Integer>>(){}.getType();
for(Map.Entry<Object,Object> item : entries.entrySet()){
Object key1 = item.getKey();
Object value = item.getValue();
result.put(gson.fromJson(gson.toJson(key1),Integer.class),gson.fromJson(value.toString(),type));
}
return result;
}
public <T> List<T> getMapEntrys(String type, String key, Collection<Object> mapKeys, Class<T> valueClazz) {
String rkey = getKey(type, key);

View File

@ -28,6 +28,7 @@ import com.ljsd.jieling.logic.mission.MissionLoigc;
import com.ljsd.jieling.logic.player.PlayerLogic;
import com.ljsd.jieling.logic.privilegeCard.PrivilegeCardLogic;
import com.ljsd.jieling.logic.question.QuestionLogic;
import com.ljsd.jieling.logic.rankReward.RankRewardLogic;
import com.ljsd.jieling.logic.store.BuyGoodsLogic;
import com.ljsd.jieling.logic.store.PushRechargeType;
import com.ljsd.jieling.logic.store.StoreLogic;
@ -222,6 +223,8 @@ public class GetPlayerInfoHandler extends BaseHandler {
StoreLogic.checkStoreDel(user); //删除已删除的商店道具
//处理启明星每日奖励发放
InvestigateLogic.getInstance().dealInvestigateReward(user);
//处理离线榜单结算
RankRewardLogic.getInstance().dealRankReward(user);
int trainTaskCurLevel = MissionLoigc.getTrainTaskCurLevel(user.getUserMissionManager());
PlayerInfoProto.GetPlayerInfoResponse getPlayerInfoResponse
= PlayerInfoProto.GetPlayerInfoResponse.newBuilder()

View File

@ -243,8 +243,10 @@ public class GlobleSystemLogic implements IEventHandler {
LOGGER.info("测试服不参与分组和跨服");
return -1;
}
//Integer group = RedisUtil.getInstence().getMapValue("",RedisKey.SERVER_SPLIT_INFO, String.valueOf(GameApplication.serverId), Integer.class);
Integer group = RedisUtil.getInstence().getMapValues(RedisKey.SERVER_SPLIT_INFO, String.class, Integer.class).get(String.valueOf(GameApplication.serverId));
Integer group = RedisUtil.getInstence().getMapValue(RedisKey.SERVER_SPLIT_INFO,"", String.valueOf(GameApplication.serverId), Integer.class);
// Map<String,Integer> groups = RedisUtil.getInstence().getMapValues(RedisKey.SERVER_SPLIT_INFO, String.class, Integer.class);
// Integer group = groups.get(String.valueOf(GameApplication.serverId));
// Integer group = RedisUtil.getInstence().getMapValues(RedisKey.SERVER_SPLIT_INFO, String.class, Integer.class).get(String.valueOf(GameApplication.serverId));
LOGGER.info("GameApplication.serverId={},group={}",GameApplication.serverId,group);
crossGroup = group == null ? -1 : group;

View File

@ -28,6 +28,8 @@ import com.ljsd.jieling.logic.mail.MailLogic;
import com.ljsd.jieling.logic.mission.GameEvent;
import com.ljsd.jieling.logic.mission.event.MissionEventDistributor;
import com.ljsd.jieling.logic.player.PlayerLogic;
import com.ljsd.jieling.logic.rankReward.RankRewardEunm;
import com.ljsd.jieling.logic.rankReward.RankRewardLogic;
import com.ljsd.jieling.network.server.WorldHelper;
import com.ljsd.jieling.network.session.ISession;
import com.ljsd.jieling.protocols.ArenaInfoProto;
@ -620,12 +622,10 @@ public class ArenaLogic {
if (curSeason == 0) {
return;
}
String title;
List<String> content = new ArrayList<>();
if (type == GlobalsDef.ARENA_DAILY_REWARD) {
title = SErrorCodeEerverConfig.getI18NMessage("arena_daily_reward_title");
type = RankRewardEunm.ARENA_DAILY.getType();
} else {
title = SErrorCodeEerverConfig.getI18NMessage("arena_season_reward_title");
type = RankRewardEunm.ARENA_SEASON.getType();
}
Set<ZSetOperations.TypedTuple<String>> arenaRankInfo = RedisUtil.getInstence().getZsetreverseRangeWithScores(RedisKey.ARENA_RANK, Integer.toString(curSeason), 0, -1);
if (arenaRankInfo == null || arenaRankInfo.isEmpty()) {
@ -637,47 +637,35 @@ public class ArenaLogic {
int rank = 1;
int nowTime = (int) (TimeUtils.now() / 1000);
//备份日志
List<Integer> userRankIds = new ArrayList<>(arenaRankInfo.size());
//备份榜单结算信息
Map<Integer,Integer> users = new HashMap<Integer,Integer>();
for (ZSetOperations.TypedTuple<String> item : arenaRankInfo) {
String value = item.getValue();
int uid = Integer.parseInt(value);
userRankIds.add(uid);
users.put(uid, rank);
rank++;
}
XxlJobHelper.log("DefTraining_RANK uid list [{}]", JSON.toJSONString(userRankIds));
for (ZSetOperations.TypedTuple<String> item : arenaRankInfo) {
String value = item.getValue();
int uid = Integer.parseInt(value);
User user = UserManager.getUser(uid, true);
//没有需要结算的用户
if(users.size() == 0){
return;
}
//备份榜单结算信息
RankRewardLogic.getInstance().markRankRewards(type, nowTime,users);
//发送奖励
boolean send = false;
for(int uid : users.keySet()){
User user = UserManager.getUserOnlyInMap(uid, true);
//用户不在线,不发送奖励,上线自己结算
if (null == user) {
continue;
}
if (type == GlobalsDef.ARENA_DAILY_REWARD) {
MissionEventDistributor.requestStart();
user.getUserMissionManager().onGameEvent(user, GameEvent.ARENA_UPDATE_RANK, rank);
ISession sessionByUid = OnlineUserManager.getSessionByUid(uid);
if (sessionByUid != null) {
MissionEventDistributor.requestEnd(sessionByUid, true);
}
}
if (type == GlobalsDef.ARENA_DAILY_REWARD) {
content = SErrorCodeEerverConfig.getI18NMessageNew("arena_daily_reward_txt", new Object[]{rank});
} else {
content = SErrorCodeEerverConfig.getI18NMessageNew("arena_season_reward_txt", new Object[]{rank});
user.getPlayerInfoManager().removeRed(GlobalsDef.ARENA_CHALLENGE_TYPE);
String key = RedisUtil.getInstence().getKey(RedisKey.ARENA_RRECORD, Integer.toString(uid));
RedisUtil.getInstence().del(key);
}
String rewardByRank = getRewardByRank(rank++, type);
if (!"".equals(rewardByRank)) {
MailLogic.getInstance().sendMail(user.getId(), title, content, rewardByRank, nowTime, Global.MAIL_EFFECTIVE_TIME);
}
//直接发奖
RankRewardLogic.getInstance().calcRankReward(user, nowTime, type);
send = true;
}
if(send){
MongoUtil.getInstence().lastUpdate();
}
MongoUtil.getInstence().lastUpdate();
}
public String getRewardByRank(int rank, int type) {

View File

@ -239,6 +239,16 @@ public class PlayerManager extends MongoBase {
public List<Integer> getTotemList() {
return totemList;
}
//榜单结算奖励信息,key为榜单类型,value为最后领取榜单结算奖励的时间,每次登录要把在结算时间范围内的榜单全部领取
public Map<Integer,Integer> rankRewards = new HashMap<Integer,Integer>();
public Map<Integer, Integer> getRankRewards() {
return rankRewards;
}
public void setRankRewards(Map<Integer, Integer> rankRewards) {
this.rankRewards = rankRewards;
updateString("rankRewards",rankRewards);
}
public void setTotemList(List<Integer> totemList) {
this.totemList = totemList;

View File

@ -0,0 +1,34 @@
package com.ljsd.jieling.logic.rankReward;
import com.ljsd.jieling.db.redis.RedisKey;
public enum RankRewardEunm {
ARENA_DAILY(1, RedisKey.AREAN_DAY_RANK_MARK),
ARENA_SEASON(2, RedisKey.AREAN_SEASON_RANK_MARK);
private int type;
private String redisKey;
RankRewardEunm(int type, String redisKey) {
this.type = type;
this.redisKey = redisKey;
}
public int getType() {
return type;
}
public String getRedisKey() {
return redisKey;
}
public static RankRewardEunm getByType(int type) {
for (RankRewardEunm e : values()) {
if (e.type == type) {
return e;
}
}
return null;
}
}

View File

@ -0,0 +1,304 @@
package com.ljsd.jieling.logic.rankReward;
import com.ljsd.GameApplication;
import com.ljsd.jieling.core.GlobalsDef;
import com.ljsd.jieling.db.redis.RedisKey;
import com.ljsd.jieling.db.redis.RedisUtil;
import com.ljsd.jieling.globals.Global;
import com.ljsd.jieling.logic.OnlineUserManager;
import com.ljsd.jieling.logic.arena.ArenaLogic;
import com.ljsd.jieling.logic.dao.root.User;
import com.ljsd.jieling.logic.mail.MailLogic;
import com.ljsd.jieling.logic.mission.GameEvent;
import com.ljsd.jieling.logic.mission.event.MissionEventDistributor;
import com.ljsd.jieling.network.session.ISession;
import config.SErrorCodeEerverConfig;
import org.apache.groovy.util.SystemUtil;
import util.TimeUtils;
import java.util.*;
/**
* 榜单结算奖励逻辑处理,包括排名和段位类的结算
*/
public class RankRewardLogic {
//按照榜单类型,结算时间,用户uid,rankValue(可以为排名,段位值,伤害,积分等)
public Map<Integer,Map<Integer, Map<Integer,Integer>>> rankMarks = new HashMap<Integer,Map<Integer,Map<Integer,Integer>>>();
//按类型存储的排好序的榜单结算时间集合
public Map<Integer,List<Integer>> timeMaps = new HashMap<Integer,List<Integer>>(); //重置时间间隔
//榜单保存最长时间
public static final int SAVE_TIME = 7 * 24 * 3600; //重置时间间隔
private RankRewardLogic() {
}
public static class Instance {
public final static RankRewardLogic instance = new RankRewardLogic();
}
public static RankRewardLogic getInstance() {
return RankRewardLogic.Instance.instance;
}
public void init() {
int now = (int) (TimeUtils.now() / 1000);
int diff = now - SAVE_TIME;
for(RankRewardEunm e : RankRewardEunm.values()){
String key = GameApplication.serverId + RedisKey.Delimiter_colon + e.getRedisKey();
Map<Integer, Map<Integer,Integer>> map = RedisUtil.getInstence().getMapValues(key);
if(map != null && map.size() > 0){
for(int time : map.keySet()){
//失效了,不添加并清除redis信息
if(time < diff){
RedisUtil.getInstence().hdel(key, String.valueOf(time));
continue;
}
Map<Integer,Integer> users = map.get(time);
if(users != null && users.size() > 0){
//添加榜单备份信息,用于结算奖励时使用
addRankMark(e.getType(),time,users,false,false);
}
}
//重置榜单结算时间顺序表,用于结算奖励时使用
this.resetTimeList(e.getType());
}
}
}
/**
* 添加一个新的榜单备份信息
* @param type
* @param time
* @param users
*/
public void addRankMark(int type, int time, Map<Integer,Integer> users,boolean save,boolean reset) {
if(!rankMarks.containsKey(type)) {
Map<Integer,Map<Integer,Integer>> map = new HashMap<Integer, Map<Integer, Integer>>();
map.put(time, users);
rankMarks.put(type, map);
if(save){
String key = GameApplication.serverId + RedisKey.Delimiter_colon + RankRewardEunm.getByType(type).getRedisKey();
RedisUtil.getInstence().putMap(key, String.valueOf(time),users);
}
if(reset){
this.resetTimeList(type);
}
return;
}
Map<Integer, Map<Integer, Integer>> map = rankMarks.get(type);
if(map == null) {
map = new HashMap<Integer, Map<Integer, Integer>>();
map.put(time, users);
rankMarks.put(type, map);
if(save){
String key = GameApplication.serverId + RedisKey.Delimiter_colon + RankRewardEunm.getByType(type).getRedisKey();
RedisUtil.getInstence().putMap(key, String.valueOf(time),users);
}
if(reset){
this.resetTimeList(type);
}
return;
}
map.put(time, users);
if(save){
String key = GameApplication.serverId + RedisKey.Delimiter_colon + RankRewardEunm.getByType(type).getRedisKey();
RedisUtil.getInstence().putMap(key, String.valueOf(time),users);
}
if(reset){
this.resetTimeList(type);
}
}
/**
* 检查榜单是否有效,及时删除
*/
public void checkEffect(int type, int time,boolean reset) {
List<Integer> list = this.getTimeList(type);
boolean isRemove = false;
if(list!= null && list.size()>0) {
for (int key : list) {
int diff = time - SAVE_TIME;
//过期了
if (diff > key) {
this.removeRankMark(type, key);
isRemove = true;
}
}
}
//有删除的记录,重置时间顺序表
if(isRemove && reset){
this.resetTimeList(type);
}
}
/**
* 删除一个榜单结算信息
* @param type
* @param time
*/
public void removeRankMark(int type, int time) {
if(!rankMarks.containsKey(type)) return;
Map<Integer, Map<Integer, Integer>> map = rankMarks.get(type);
if(map!= null){
map.remove(time);
String key = GameApplication.serverId + RedisKey.Delimiter_colon + RankRewardEunm.getByType(type).getRedisKey();
RedisUtil.getInstence().hdel(key, String.valueOf(time));
}
}
/**记录榜单结算信息
*
* @param type
* @param time
* @param users
*/
public void markRankRewards(int type, int time, Map<Integer,Integer> users){
//处理失效榜单
this.checkEffect(type, time,false);
//添加新榜单
this.addRankMark(type, time, users,true,true);
}
/**
* 重置榜单结算时间顺序表
* @param type
*/
public void resetTimeList(int type){
List<Integer> list = new ArrayList<Integer>();
if(rankMarks.containsKey(type) ){
Map<Integer, Map<Integer, Integer>> map = rankMarks.get(type);
if(map!= null){
for(int key : map.keySet()){
list.add(key);
}
}
}
if(list.size() > 0){
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
}
this.timeMaps.put(type, list);
}
/**
* 获取某个类型的榜单结算时间集合
* @param type
* @return
*/
public List<Integer> getTimeList(int type){
if(timeMaps.containsKey(type)){
return timeMaps.get(type);
}
return null;
}
/**
* 结算榜单奖励
* @param user
*/
public void calcRankReward(User user,int now,int type) throws Exception {
//对象级同步,防止定时结算和登录结算同时进行,数据错误
synchronized (user){
Map<Integer,Integer> rankRewards = user.getPlayerInfoManager().getRankRewards();
int time = 0;
if(rankRewards.containsKey(type)){
time = rankRewards.get(type);
}
int calcTime = 0;
boolean isCalc = false;
boolean isRemove = false;
Map<Integer, Map<Integer, Integer>> map = rankMarks.get(type);
List<Integer> list = this.getTimeList(type);
if(list!= null && list.size()>0){
for(int key : list){
//取最近的时间作为结算时间,如果之前取当前时间可能会出现榜单正在备份,但是玩家结算时间超过了榜单备份的时间,导致该奖励无法结算
if(calcTime < key){
calcTime = key;
}
int diff = now - SAVE_TIME;
//过期了
if(diff > key){
this.removeRankMark(type, key);
isRemove = true;
continue;
}
//已经结算过了,最近的都结算过了,前面的没必要再检查了
if(key <= time){
break;
}
Map<Integer,Integer> users = map.get(key);
if(users == null){
continue;
}
if(!users.containsKey(user.getId())){
continue;
}
int userId = user.getId();
int rank = users.get(userId);
if(type == RankRewardEunm.ARENA_DAILY.getType()){
//竞技场每日排行奖励
String title = SErrorCodeEerverConfig.getI18NMessage("arena_daily_reward_title");
List<String> content = SErrorCodeEerverConfig.getI18NMessageNew("arena_daily_reward_txt", new Object[]{rank});
String rewardByRank = ArenaLogic.getInstance().getRewardByRank(rank, GlobalsDef.ARENA_DAILY_REWARD);
if (!"".equals(rewardByRank)) {
MailLogic.getInstance().sendMail(user.getId(), title, content, rewardByRank, key, Global.MAIL_EFFECTIVE_TIME);
}
//如果有错误的排名奖励,则认为是已经结算过了,防止错误的排行导致一直循环检查
isCalc = true;
MissionEventDistributor.requestStart();
user.getUserMissionManager().onGameEvent(user, GameEvent.ARENA_UPDATE_RANK, rank);
ISession sessionByUid = OnlineUserManager.getSessionByUid(userId);
if (sessionByUid != null) {
MissionEventDistributor.requestEnd(sessionByUid, true);
}
}else if(type == RankRewardEunm.ARENA_SEASON.getType()){
//竞技场赛季奖励
String title = SErrorCodeEerverConfig.getI18NMessage("arena_season_reward_title");
List<String> content = SErrorCodeEerverConfig.getI18NMessageNew("arena_season_reward_txt", new Object[]{rank});
content = SErrorCodeEerverConfig.getI18NMessageNew("arena_season_reward_txt", new Object[]{rank});
user.getPlayerInfoManager().removeRed(GlobalsDef.ARENA_CHALLENGE_TYPE);
String rkey = RedisUtil.getInstence().getKey(RedisKey.ARENA_RRECORD, Integer.toString(userId));
RedisUtil.getInstence().del(rkey);
String rewardByRank = ArenaLogic.getInstance().getRewardByRank(rank, GlobalsDef.ARENA_SEASON_REWARD);
if (!"".equals(rewardByRank)) {
MailLogic.getInstance().sendMail(user.getId(), title, content, rewardByRank, key, Global.MAIL_EFFECTIVE_TIME);
}
//如果有错误的排名奖励,则认为是已经结算过了,防止错误的排行导致一直循环检查
isCalc = true;
}
}
}
//有结算的奖励
if(isCalc && calcTime > 0){
rankRewards.put(type, now);
user.getPlayerInfoManager().setRankRewards(rankRewards);
}
//有删除的榜单时间记录,则重置榜单结算时间顺序表
if(isRemove){
this.resetTimeList(type);
}
}
}
/**
* 登录处理
* @param user
* @throws Exception
*/
public void dealRankReward(User user) throws Exception {
int now = (int) (TimeUtils.now() / 1000);
//结算竞技场每日奖励
int type = RankRewardEunm.ARENA_DAILY.getType();
this.calcRankReward(user, now, type);
//结算段位赛每日奖励
type = RankRewardEunm.ARENA_SEASON.getType();
this.calcRankReward(user, now, type);
}
}

View File

@ -4,6 +4,7 @@ import com.ljsd.GameApplication;
import com.ljsd.fight.CheckFight;
import com.ljsd.jieling.config.json.CoreSettings;
import com.ljsd.jieling.core.CoreLogic;
import com.ljsd.jieling.core.GlobalsDef;
import com.ljsd.jieling.db.mongo.AreaManager;
import com.ljsd.jieling.db.mongo.MongoUtil;
import com.ljsd.jieling.db.redis.RedisKey;
@ -31,6 +32,7 @@ import manager.STableManager;
import org.apache.dubbo.common.utils.NetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.TimeUtils;
/**
* 分钟定时器
@ -59,6 +61,10 @@ public class MinuteTask extends Thread {
}
try {
AreaManager.getInstance().processAddServerAreaIsChange();
if (TimeUtils.getHourOfDay() == 21 && TimeUtils.getMinute() == 0) {
// 竞技场,每日晚九点刷新
ArenaLogic.getInstance().sendReward(GlobalsDef.ARENA_DAILY_REWARD);
}
} catch (Exception e) {
LOGGER.error("Exception::2=>{}", e.toString());
}