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
}
}