Files
hjha-server/GlobalSever/Manager/PropManager_Cache.cs
xiaoou e9616125ce feat: initial commit - HJHA game server full source
6 major server modules (PdkFriendServer/GlobalSever/ServerCore/GameModule/GameNetModule) +
game logic (GameFix/GameDAL/ServerData) +
network layer (NetWorkMessage) +
data layer (ObjectModel) +
utilities (MrWu/Core/Config/CloudAPI/dll) +
adapters (zyxAdapter/base)

.NET 8.0 C# solution, 16 projects, 958 source files
2026-07-07 12:02:15 +08:00

549 lines
19 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using ActorCore;
using GameData;
using NetWorkMessage;
using ObjectModel.User;
using Server.Config;
using Server.Core;
using Server.Data;
using Server.DB.Redis;
using Server.DB.Sql;
using StackExchange.Redis;
using Debug = MrWu.Debug.Debug;
namespace Server
{
public partial class PropManager
{
/// <summary>
/// 道具数据缓存在Redis中的用户
/// </summary>
public static readonly RedisKey UserPropsCacheUserIdKey = "UserPropsCacheUserId";
/// <summary>
/// 变化值
/// </summary>
private const string ChangeFiledName = "Change";
private MainActor _MainActor;
public MainActor MainActor
{
get
{
if (_MainActor == null)
{
_MainActor = ActorManager.Instance.Get(ActorTypeId.Main) as MainActor;
}
return _MainActor;
}
}
public ActorId GameCenterMainActorId { get; private set; }
public Task<UserPropData> GetUserPropDataAsync(int userId)
{
return Task.Run(() => GetUserPropData(userId));
}
/// <summary>
/// 获取玩家的道具数据
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public UserPropData GetUserPropData(int userId)
{
//先读取内存
if (UserPropCache.TryGetValue(userId, out UserPropData data))
{
return data;
}
//内存有则获取内存中的数据没有则读取Redis再没有就读取SqlServer
data = ReadUserPropDataRedis(userId);
if (data != null)
{
SaveUser2Memory(data);
return data;
}
//还没有则读取SqlServer
Dictionary<int, int> dataDic = SelectUserProp(userId);
data = new UserPropData(userId);
data.Init(dataDic);
SaveUser2Memory(data);
SaveOriginUserPropDataRedis(userId, dataDic);
return data;
}
/// <summary>
/// 保存玩家数据到内存
/// </summary>
/// <param name="data"></param>
private void SaveUser2Memory(UserPropData data)
{
UserPropCache.TryAdd(data.UserId, data);
}
/// <summary>
/// 通知其他服务器玩家道具数据变化
/// </summary>
/// <param name="userId"></param>
public void NotifyOtherPropChange(int userId)
{
if (!ServerInfoHelper.IsGameServer)
{
//通知游戏
_ = NotifyPropChange2Game(userId);
}
if (!ServerInfoHelper.IsGameCenterServer)
{
//通知中心服务器
NotifyPropChange2GameCenter(userId);
}
}
private async Task NotifyPropChange2Game(int userid)
{
MainActor mainActor = MainActor;
if (mainActor != null)
{
PlayerLockInfo playerLockInfo = await PlayerFun.GetPlayerLockInfoAsync(userid);
if (playerLockInfo != null && playerLockInfo.util != null)
{
ActorId actorId = new ActorId((byte)playerLockInfo.util.id, (byte)playerLockInfo.util.nodeNum,
ActorTypeId.Main);
mainActor.Send(actorId, UserPropChangeMessage.Create(userid));
}
}
}
private void NotifyPropChange2GameCenter(int userid)
{
MainActor mainActor = MainActor;
if (mainActor != null)
{
mainActor.Send(GameCenterMainActorId, UserPropChangeMessage.Create(userid));
}
}
/// <summary>
/// 保存变化值到Redis
/// </summary>
/// <param name="userId">玩家的UserId</param>
/// <param name="clearCache">清理缓存</param>
public bool SaveUserPropDataRedis(int userId, bool clearCache)
{
bool isChange = false;
//保存变化值到Redis
if (UserPropCache.TryGetValue(userId, out UserPropData data))
{
if (data.IsChange())
{
Dictionary<int, int> valueChange = new Dictionary<int, int>();
try
{
//保存变化值到Redis并更新内存
string key = GetRedisKey(userId);
IBatch batch = redisManager.db.CreateBatch();
foreach (var kv in data.PropChangeDic)
{
if (kv.Value != 0)
{
batch.HashIncrementAsync(key, $"{ChangeFiledName}{kv.Key}", kv.Value);
valueChange.Add(kv.Key, kv.Value);
}
}
batch.SortedSetAddAsync(UserPropsCacheUserIdKey, userId, TimeInfo.Instance.FrameTime);
batch.Execute();
data.UpdateChangeData(valueChange);
NotifyOtherPropChange(userId);
isChange = true;
}
catch (Exception e)
{
Debug.Fatal($"保存玩家道具失败! {e.Message} {JsonPack.GetJson(valueChange)}");
}
}
}
if (clearCache)
{
DeleteMemoryCache(userId);
}
return isChange;
}
/// <summary>
/// 删除本地内存缓存
/// </summary>
/// <param name="userid"></param>
private void DeleteMemoryCache(int userid)
{
UserPropCache.TryRemove(userid, out _);
}
/// <summary>
/// 保存原始数据到Redis
/// </summary>
/// <param name="userid"></param>
/// <param name="dataDic"></param>
public void SaveOriginUserPropDataRedis(int userid, Dictionary<int, int> dataDic)
{
if (dataDic.Count <= 0)
{
return;
}
string key = GetRedisKey(userid);
var tran = redisManager.GetDataBase().CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(key));
List<HashEntry> hashEntries = new List<HashEntry>(dataDic.Count);
foreach (var kv in dataDic)
{
hashEntries.Add(new HashEntry(kv.Key.ToString(), kv.Value.ToString()));
}
tran.HashSetAsync(key, hashEntries.ToArray());
tran.SortedSetAddAsync(UserPropsCacheUserIdKey, userid, TimeInfo.Instance.FrameTime);
tran.Execute();
}
//Redis 缓存
public string GetRedisKey(int userId)
{
return $"Game:PropData:{userId}";
}
public string GetTempRedisKey(int userId)
{
return $"tmep:{GetRedisKey(userId)}:{System.Guid.NewGuid():N}";
}
public async Task<UserPropData> ReadUserPropDataChangeValue(int userId, string tempKey)
{
string key = GetRedisKey(userId);
Debug.Info($"临时Key{tempKey}");
bool renamed;
try
{
// key 不存在时会返回 false不同 SE.Redis 版本行为略有差异,异常也兜底)
renamed = await redisManager.db.KeyRenameAsync(key, tempKey, When.NotExists);
if (renamed)
{
redisManager.db.KeyExpireAsync(tempKey, TimeSpan.FromDays(30));
}
}
catch (Exception e)
{
renamed = false;
Debug.Error($"重命名报错:{e.Message}");
}
if (!renamed)
{
Debug.Error("重命名不成功!");
return null;
}
HashEntry[] hashEntries = await redisManager.db.HashGetAllAsync(tempKey);
UserPropData data = new UserPropData(userId);
foreach (var hashEntry in hashEntries)
{
string filedName = hashEntry.Name;
if (filedName.StartsWith(ChangeFiledName))
{
filedName = filedName.Substring(ChangeFiledName.Length);
if (int.TryParse(filedName, out int propId) && int.TryParse(hashEntry.Value, out int count))
{
data.PropChangeDic.AddOrUpdate(propId, count, (key, oldValue) => oldValue + count);
}
else
{
Debug.Error($"玩家的道具数据格式有问题:{userId}");
return null;
}
}
}
return data;
}
public UserPropData ReadUserPropDataRedis(int userId, HashEntry[] hashEntries)
{
if (hashEntries == null || hashEntries.Length == 0)
{
return null;
}
UserPropData data = new UserPropData(userId);
foreach (var hashEntry in hashEntries)
{
string filedName = hashEntry.Name;
if (filedName.StartsWith(ChangeFiledName))
{
filedName = filedName.Substring(ChangeFiledName.Length);
if (int.TryParse(filedName, out int propId) && int.TryParse(hashEntry.Value, out int count))
{
data.PropDic.AddOrUpdate(propId, count, (key, oldValue) => oldValue + count);
}
else
{
Debug.Error($"玩家的道具数据格式有问题:{userId}");
return null;
}
}
else
{
if (int.TryParse(hashEntry.Name, out int propId) && int.TryParse(hashEntry.Value, out int count))
{
data.PropDic.AddOrUpdate(propId, count, (key, oldValue) => oldValue + count);
}
else
{
Debug.Error($"玩家的道具数据格式有问题:{userId}");
return null;
}
}
}
return data;
}
public UserPropData ReadUserPropDataRedis(int userId)
{
string key = GetRedisKey(userId);
HashEntry[] hashEntries = redisManager.db.HashGetAll(key);
return ReadUserPropDataRedis(userId, hashEntries);
}
private void OnUserPropChange(object param)
{
if (param is UserPropChangeMessage userPropChangeMessage)
{
Debug.Info($"玩家道具变化:{userPropChangeMessage.UserId} {Thread.CurrentThread.ManagedThreadId}");
SaveUserPropDataRedis(userPropChangeMessage.UserId, true);
}
}
private void SaveUserPropCaches()
{
foreach (var userid in UserPropCache.Keys)
{
if (SaveUserPropDataRedis(userid, false))
{
Debug.ImportantLog($"保存玩家道具数据:{userid}");
}
}
}
//内存缓存 只有游戏和中心服务器才有缓存 玩家的内存道具缓存
public readonly ConcurrentDictionary<int, UserPropData> UserPropCache =
new ConcurrentDictionary<int, UserPropData>();
/// <summary>
/// 玩家的道具数据
/// </summary>
public class UserPropData
{
/// <summary>
/// 玩家ID
/// </summary>
public readonly int UserId;
/// <summary>
/// 玩家的道具数据
/// </summary>
public readonly ConcurrentDictionary<int, int> PropDic = new ConcurrentDictionary<int, int>();
/// <summary>
/// 玩家的道具变化数据
/// </summary>
public readonly ConcurrentDictionary<int, int> PropChangeDic = new ConcurrentDictionary<int, int>();
public UserPropData(int userid)
{
UserId = userid;
}
public int GetPropCount(PropType propType)
{
return GetPropCount((int)propType);
}
private int GetPropCount(int propType)
{
if (!PropDic.TryGetValue(propType, out int count))
{
count = 0;
}
if (PropChangeDic.TryGetValue(propType, out int changeCount))
{
count += changeCount;
}
return count;
}
public void AddGoldLog(long changeCount, long finalValue, UserGoldLogType propType, string tag)
{
Instance.AddGoldLog(UserId, changeCount, finalValue, propType, tag);
}
public void AddLog(int propType, long changeCount, long finalValue, string tag)
{
Instance.AddLog(UserId, propType, changeCount, finalValue, tag);
}
public void AddLogs(List<ZiChanLogData> logs)
{
Instance.AddLog(UserId,logs);
}
public void AddProp(PropType propType, int count, string tag)
{
AddProp((int)propType, count, tag);
}
internal void AddProp(int propType, int count, string tag)
{
if (count == 0)
{
return;
}
PropChangeDic.AddOrUpdate(propType, count, (key, oldValue) => oldValue + count);
int newValue = GetPropCount((PropType)propType);
Instance.AddLog(UserId, propType, count, newValue, tag);
}
public void UpdateChangeData(Dictionary<int, int> changeData)
{
//更新变化值到内存
foreach (var kv in changeData)
{
PropChangeDic.AddOrUpdate(kv.Key, kv.Value, (key, oldValue) => oldValue - kv.Value);
PropDic.AddOrUpdate(kv.Key, kv.Value, (key, oldValue) => oldValue + kv.Value);
}
}
public void Init(Dictionary<int, int> dic)
{
foreach (var kv in dic)
{
PropDic.AddOrUpdate(kv.Key, kv.Value, (key, oldValue) => kv.Value);
}
}
/// <summary>
/// 是否有变化
/// </summary>
/// <returns></returns>
public bool IsChange()
{
if (PropChangeDic.Count == 0)
{
return false;
}
foreach (var value in PropChangeDic.Values)
{
if (value != 0)
{
return true;
}
}
return false;
}
public DaoJu GetOldDaoJu()
{
DaoJu daoJu = new DaoJu();
daoJu.userid = UserId;
daoJu.cansaij = this.GetPropCount(PropType.MatchCard);
daoJu.fuhuoka = this.GetPropCount(PropType.ReviveCard);
daoJu.clubtoken = this.GetPropCount(PropType.ClubToken);
daoJu.clubsupertoken = this.GetPropCount(PropType.ClubSuperToken);
return daoJu;
}
public Dictionary<int, int> GetAllProps()
{
Dictionary<int,int> dic = new Dictionary<int, int>(PropDic.Count);
foreach (var kv in PropDic.Keys)
{
dic[kv] = this.GetPropCount((PropType)kv);
}
foreach (var kv in PropChangeDic.Keys)
{
if (!dic.ContainsKey(kv))
{
dic[kv] = this.GetPropCount((PropType)kv);
}
}
return dic;
}
}
#region
//登录 + 主动转换,转换完成后,删除此处代码
public Task ChangePropDataAsync(int userid,bool isClearCache)
{
return Task.Run(() => ChangePropData(userid,isClearCache));
}
private void ChangePropData(int userid,bool isClearCache)
{
string sql = $"select * from tbDaoJu2023 where userid = {userid}";
DataSet ds = new DataSet();
GameDB.Instance.ExceSql(dbbase.gamedb, sql, null, ds);
if (ds.Tables.Count <= 0 || ds.Tables[0].Rows.Count <= 0)
{
return;
}
//转换道具数据
int cansaij = (int)ds.Tables[0].Rows[0]["cansaij"];
int fuhuoka = (int)ds.Tables[0].Rows[0]["fuhuoka"];
int clubtoken = (int)ds.Tables[0].Rows[0]["clubtoken"];
int clubsupertoken = (int)ds.Tables[0].Rows[0]["clubsupertoken"];
if (cansaij > 0 || fuhuoka > 0 || clubtoken > 0 || clubsupertoken > 0)
{
string updateSql = $"update tbDaoJu2023 set cansaij = cansaij - {cansaij}, fuhuoka = fuhuoka - {fuhuoka}, clubtoken = clubtoken - {clubtoken}, clubsupertoken = clubsupertoken - {clubsupertoken} where userid = {userid}";
GameDB.Instance.ExceSql(dbbase.gamedb, updateSql);
UserPropData userPropData = PropManager.Instance.GetUserPropData(userid);
userPropData.AddProp(PropType.MatchCard,cansaij,"道具转换");
userPropData.AddProp(PropType.ReviveCard,fuhuoka,"道具转换");
userPropData.AddProp(PropType.ClubToken,clubtoken,"道具转换");
userPropData.AddProp(PropType.ClubSuperToken,clubsupertoken,"道具转换");
PropManager.Instance.SaveUserPropDataRedis(userid, isClearCache);
Debug.ImportantLog($"道具转换 userid:{userid} cansaij:{cansaij} fuhuoka:{fuhuoka} clubtoken:{clubtoken} clubsupertoken:{clubsupertoken}");
}
}
#endregion
}
}