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
549 lines
19 KiB
C#
549 lines
19 KiB
C#
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
|
||
}
|
||
} |