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 { /// /// 道具数据缓存在Redis中的用户 /// public static readonly RedisKey UserPropsCacheUserIdKey = "UserPropsCacheUserId"; /// /// 变化值 /// 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 GetUserPropDataAsync(int userId) { return Task.Run(() => GetUserPropData(userId)); } /// /// 获取玩家的道具数据 /// /// /// 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 dataDic = SelectUserProp(userId); data = new UserPropData(userId); data.Init(dataDic); SaveUser2Memory(data); SaveOriginUserPropDataRedis(userId, dataDic); return data; } /// /// 保存玩家数据到内存 /// /// private void SaveUser2Memory(UserPropData data) { UserPropCache.TryAdd(data.UserId, data); } /// /// 通知其他服务器玩家道具数据变化 /// /// 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)); } } /// /// 保存变化值到Redis /// /// 玩家的UserId /// 清理缓存 public bool SaveUserPropDataRedis(int userId, bool clearCache) { bool isChange = false; //保存变化值到Redis if (UserPropCache.TryGetValue(userId, out UserPropData data)) { if (data.IsChange()) { Dictionary valueChange = new Dictionary(); 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; } /// /// 删除本地内存缓存 /// /// private void DeleteMemoryCache(int userid) { UserPropCache.TryRemove(userid, out _); } /// /// 保存原始数据到Redis /// /// /// public void SaveOriginUserPropDataRedis(int userid, Dictionary dataDic) { if (dataDic.Count <= 0) { return; } string key = GetRedisKey(userid); var tran = redisManager.GetDataBase().CreateTransaction(); tran.AddCondition(Condition.KeyNotExists(key)); List hashEntries = new List(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 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 UserPropCache = new ConcurrentDictionary(); /// /// 玩家的道具数据 /// public class UserPropData { /// /// 玩家ID /// public readonly int UserId; /// /// 玩家的道具数据 /// public readonly ConcurrentDictionary PropDic = new ConcurrentDictionary(); /// /// 玩家的道具变化数据 /// public readonly ConcurrentDictionary PropChangeDic = new ConcurrentDictionary(); 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 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 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 dic) { foreach (var kv in dic) { PropDic.AddOrUpdate(kv.Key, kv.Value, (key, oldValue) => kv.Value); } } /// /// 是否有变化 /// /// 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 GetAllProps() { Dictionary dic = new Dictionary(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 } }