feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
using game.PlayerDataStorage;
|
||||
using asap.core;
|
||||
using asap.playfab.async;
|
||||
using GameCore;
|
||||
using PlayFab.ClientModels;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace game
|
||||
{
|
||||
public class FriendService
|
||||
{
|
||||
[Inject]
|
||||
public IUserService userService { get; set; }
|
||||
|
||||
[Inject]
|
||||
public ICustomServerMgr customServerMgr { get; set; }
|
||||
|
||||
[Inject]
|
||||
public IRTService rts { get; set; }
|
||||
|
||||
[Inject]
|
||||
public cfg.Tables tables { get; set; }
|
||||
|
||||
[Inject] public IPlayerSaveStore SaveStore { get; set; }
|
||||
public bool IsCurrent => SaveStore.IsCurrent && !userService.LoginLifetime.IsCancellationRequested;
|
||||
bool linkRewardPending;
|
||||
|
||||
public const string InviteLinkRewardCountKey = "InviteLinkRewardCount";
|
||||
IDisposable linkCompletedDisposable;
|
||||
|
||||
public void GetFriendData()
|
||||
{
|
||||
//GetFriendLeaderboard();
|
||||
GetFriendList();
|
||||
//GetInvitedListCount();
|
||||
}
|
||||
|
||||
#region 邀请Link相关
|
||||
public int InviteLinkNumber { private set; get; }
|
||||
int InviteLinkRewardCount = 0;
|
||||
|
||||
|
||||
void SetLinkAwardData()
|
||||
{
|
||||
List<cfg.Invite> invites = tables.TbInvite.DataList;
|
||||
//已经领取奖励的数量
|
||||
var read = SaveStore.Read(InviteLinkRewardCountKey);
|
||||
var value = read.ValueOrThrow();
|
||||
if (read.Found == false) InviteLinkRewardCount = 0;
|
||||
else if (!int.TryParse(value, out InviteLinkRewardCount) || InviteLinkRewardCount < 0 || InviteLinkRewardCount > invites.Count)
|
||||
throw new InvalidOperationException("Invalid invitation reward count.");
|
||||
int canRewardCount = InviteLinkRewardCount;
|
||||
for (int i = InviteLinkRewardCount; i < invites.Count; i++)
|
||||
{
|
||||
//领取奖励
|
||||
if (invites[i].NumInvite <= InviteLinkNumber)
|
||||
{
|
||||
canRewardCount = i + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Invite, canRewardCount > InviteLinkRewardCount);
|
||||
#if AGG
|
||||
using (var e = GEvent.GameEvent("invite_increase"))
|
||||
{
|
||||
e.AddContent("inviteprogress", InviteLinkNumber)
|
||||
.AddContent("rewardprogress", InviteLinkRewardCount);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<int> OnReceiveLinkAward()
|
||||
{
|
||||
if (!IsCurrent) throw new OperationCanceledException();
|
||||
if (linkRewardPending) throw new InvalidOperationException("Invitation reward is pending or requires recovery.");
|
||||
SetLinkAwardData();
|
||||
int canRewardCount = InviteLinkRewardCount;
|
||||
List<cfg.Invite> invites = tables.TbInvite.DataList;
|
||||
List<int> DropIDs = new List<int>();
|
||||
for (int i = InviteLinkRewardCount; i < invites.Count; i++)
|
||||
{
|
||||
//领取奖励
|
||||
if (invites[i].NumInvite <= InviteLinkNumber)
|
||||
{
|
||||
canRewardCount = i + 1;
|
||||
DropIDs.Add(invites[i].DropID);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (InviteLinkRewardCount != canRewardCount)
|
||||
{
|
||||
linkRewardPending = true;
|
||||
var items = GContext.container.Resolve<PlayerItemData>();
|
||||
var player = GContext.container.Resolve<PlayerData>();
|
||||
await PlayerSaveAction.RunAsync(SaveStore, "invite-reward", () =>
|
||||
{
|
||||
items.AddItemByDropList(DropIDs, null);
|
||||
PlayerSaveAction.Present(() => GContext.Publish(new ShowData()));
|
||||
InviteLinkRewardCount = canRewardCount;
|
||||
SaveStore.Write(InviteLinkRewardCountKey, InviteLinkRewardCount.ToString()).RequireAccepted();
|
||||
}, () => player.SavePlayerData(), userService.LoginLifetime);
|
||||
if (!IsCurrent) throw new OperationCanceledException();
|
||||
linkRewardPending = false;
|
||||
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Invite, false);
|
||||
#if AGG
|
||||
using (var e = GEvent.GameEvent("invite_reward"))
|
||||
{
|
||||
e.AddContent("inviteprogress", InviteLinkNumber)
|
||||
.AddContent("rewardprogress", InviteLinkRewardCount);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return InviteLinkRewardCount;
|
||||
}
|
||||
#endregion 邀请Link相关
|
||||
|
||||
#region 判断是否好友
|
||||
public bool IsFriend(string playFabID)
|
||||
{
|
||||
if (m_FriendList == null || m_FriendList.Count < 1 || string.IsNullOrEmpty(playFabID)) return false;
|
||||
|
||||
foreach (var friend in m_FriendList)
|
||||
{
|
||||
if (friend.playFabId.Equals(playFabID))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取好友列表
|
||||
private List<LeadboardItemData> m_FriendList = new List<LeadboardItemData>();
|
||||
public List<LeadboardItemData> FriendList { get { return m_FriendList; } }
|
||||
public bool IsFriendListRefreshing { get; private set; } = true;
|
||||
public async void GetFriendList()
|
||||
{
|
||||
GetFriendsListRequest getFriendsListRequest = new GetFriendsListRequest
|
||||
{
|
||||
ProfileConstraints = new PlayerProfileViewConstraints()
|
||||
{
|
||||
ShowAvatarUrl = true,
|
||||
ShowDisplayName = true,
|
||||
ShowStatistics = true,
|
||||
ShowLastLogin = true,
|
||||
}
|
||||
};
|
||||
|
||||
var result = await PlayFabClientAsyncAPI.GetFriendsListAsync(getFriendsListRequest);
|
||||
var list = result?.Friends;
|
||||
if (list != null)
|
||||
{
|
||||
string userId = GContext.container.Resolve<IUserService>().UserId;
|
||||
List<LeadboardItemData> leadboardList = new List<LeadboardItemData>();
|
||||
int count = list.Count;
|
||||
foreach (FriendInfo friendInfo in list)
|
||||
{
|
||||
if (friendInfo.Profile == null) continue;
|
||||
var listStatistic = friendInfo.Profile.Statistics;
|
||||
if (listStatistic == null) continue;
|
||||
|
||||
if (userId.Equals(friendInfo.FriendPlayFabId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(friendInfo.Profile.DisplayName))
|
||||
{
|
||||
friendInfo.Profile.DisplayName = userService.GetDefaultName(friendInfo.FriendPlayFabId);
|
||||
}
|
||||
|
||||
int level = 0;
|
||||
foreach (var item in listStatistic)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (item.Name == LeadboardData.leadboardLevelName)
|
||||
{
|
||||
//level = item.Value / LeadboardData.LV_MODELING;
|
||||
level = item.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
userService.SetPlayInfo(friendInfo.FriendPlayFabId, friendInfo.Profile.DisplayName, friendInfo.Profile.AvatarUrl);
|
||||
|
||||
LeadboardItemData friendData = new LeadboardItemData();
|
||||
friendData.playFabId = friendInfo.FriendPlayFabId;
|
||||
friendData.displayName = friendInfo.Profile.DisplayName;
|
||||
friendData.avatarUrl = friendInfo.Profile.AvatarUrl;
|
||||
friendData.LastLogin = friendInfo.Profile.LastLogin;
|
||||
friendData.value = level;
|
||||
friendData.position = 0;
|
||||
friendData.rank = 0;
|
||||
|
||||
leadboardList.Add(friendData);
|
||||
}
|
||||
|
||||
leadboardList.Sort((item1, item2) =>
|
||||
{
|
||||
return item2.value.CompareTo(item1.value);
|
||||
});
|
||||
|
||||
count = leadboardList.Count;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
LeadboardItemData itemData = leadboardList[i];
|
||||
itemData.position = i;
|
||||
itemData.rank = i + 1;
|
||||
}
|
||||
|
||||
m_FriendList = leadboardList;
|
||||
IsFriendListRefreshing = false;
|
||||
|
||||
GContext.Publish<SocialFriendListChangedData>(new SocialFriendListChangedData());
|
||||
#if AGG
|
||||
GEvent.SetProp("friends_count", count);
|
||||
GEvent.SetUserProp("friends_count", count);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 邀请好友
|
||||
public async Task<string> AddFriend(string playfabID)
|
||||
{
|
||||
int count = m_FriendList.Count;
|
||||
AddFriendReq getFriendListReq = new AddFriendReq()
|
||||
{
|
||||
MySelf = new BFriendInfo
|
||||
{
|
||||
PlayFabId = userService.UserId,
|
||||
AvatarUrl = userService.AvatarUrl,
|
||||
DisplayName = userService.DisplayName,
|
||||
Level = GContext.container.Resolve<PlayerData>().lv
|
||||
},
|
||||
MyFriendCount = count,
|
||||
FriendPlayFabId = playfabID,
|
||||
};
|
||||
|
||||
FriendHelper.FixBFriendInfoData(getFriendListReq.MySelf);
|
||||
|
||||
return await customServerMgr.FriendRequest("AddFriend", getFriendListReq);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 删除好友
|
||||
public async Task<bool> DeleteFriend(string friendPlayfabID)
|
||||
{
|
||||
var req = new FriendDeleteReq()
|
||||
{
|
||||
FriendPlayFabId = friendPlayfabID,
|
||||
};
|
||||
var json = await customServerMgr.FriendRequest("DeleteFriend", req);
|
||||
if (string.IsNullOrEmpty(json)) return false;
|
||||
var resp = Newtonsoft.Json.JsonConvert.DeserializeObject<FriendDeleteResp>(json);
|
||||
if (resp.Code == FriendHelper.Code_OK)
|
||||
{
|
||||
m_FriendList.RemoveAll(f => f.playFabId == friendPlayfabID);
|
||||
GContext.Publish<SocialFriendListChangedData>(new SocialFriendListChangedData());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 搜索好友
|
||||
public async Task<string> SearchFriend(string playfabID)
|
||||
{
|
||||
SearchFriendReq searchFriendReq = new SearchFriendReq()
|
||||
{
|
||||
PlayFabId = playfabID
|
||||
};
|
||||
|
||||
return await customServerMgr.FriendRequest("SearchFriend", searchFriendReq);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 接受好友邀请/忽略好友邀请
|
||||
// status:1:同意邀请;2:忽略, playfabID 接受/忽略 friendPlayFabID 的邀请
|
||||
public async Task<string> AcceptFriend(int status, string playfabID, string friendPlayFabID)
|
||||
{
|
||||
AcceptFriendReq acceptFriendReq = new AcceptFriendReq()
|
||||
{
|
||||
Status = status,
|
||||
PlayFabId = playfabID,
|
||||
FriendPlayFabId = friendPlayFabID
|
||||
};
|
||||
|
||||
return await customServerMgr.FriendRequest("AcceptFriend", acceptFriendReq);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 好友主页
|
||||
public async Task<string> GetHomePage(string playfabID, int pageNo)
|
||||
{
|
||||
GetHomePageReq getHomePageReq = new GetHomePageReq()
|
||||
{
|
||||
PlayFabId = playfabID,
|
||||
PageNo = pageNo
|
||||
};
|
||||
|
||||
return await customServerMgr.FriendRequest("GetHomePage", getHomePageReq);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 好友邀请列表&推荐列表
|
||||
public async Task<string> GetRecommendedFriends(string playfabID, int pageNo)
|
||||
{
|
||||
GetRecommendedFriendsReq acceptFriendReq = new GetRecommendedFriendsReq()
|
||||
{
|
||||
PlayFabId = playfabID,
|
||||
PageNo = pageNo
|
||||
};
|
||||
|
||||
return await customServerMgr.FriendRequest("GetRecommendedFriends", acceptFriendReq);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取申请成为我的朋友的列表
|
||||
public async Task<string> GetInvitedList()
|
||||
{
|
||||
return await customServerMgr.FriendRequest("GetInvitedList", "");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取邀请自己的列表数量
|
||||
public async void GetInvitedListCount()
|
||||
{
|
||||
if (!IsCurrent) return;
|
||||
string json = await customServerMgr.FriendRequest("GetInvitedListCount", "");
|
||||
if (!IsCurrent) return;
|
||||
if (!string.IsNullOrEmpty(json))
|
||||
{
|
||||
GetInvitedListCountResp getRecommendedFriendsResp = Newtonsoft.Json.JsonConvert.DeserializeObject<GetInvitedListCountResp>(json);
|
||||
if (getRecommendedFriendsResp != null)
|
||||
{
|
||||
if (getRecommendedFriendsResp.FriendInvitedCount >= 0)
|
||||
{
|
||||
await Awaiters.NextFrame;
|
||||
if (!IsCurrent) return;
|
||||
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Friend_Add, getRecommendedFriendsResp.FriendInvitedCount > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[FriendService::GetInvitedListCount] FriendInvitedCount Failed:");
|
||||
}
|
||||
|
||||
if (getRecommendedFriendsResp.DeepLinkInvitedCount > 0)
|
||||
{
|
||||
InviteLinkNumber = getRecommendedFriendsResp.DeepLinkInvitedCount;
|
||||
SetLinkAwardData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 邀请码处理
|
||||
const string chars = "0M4KL3FZSTG8XCY5E6VW179A2UBDHQRIJNOP";
|
||||
readonly ulong baseLength = (ulong)chars.Length;
|
||||
public string EncryptUlongToInvitationCode(ulong number)
|
||||
{
|
||||
StringBuilder encoded = new StringBuilder();
|
||||
while (number > 0)
|
||||
{
|
||||
encoded.Insert(0, chars[(int)(number % baseLength)]);
|
||||
number /= baseLength;
|
||||
}
|
||||
return encoded.ToString().PadLeft(14, chars[0]); // Pad with '0' if necessary
|
||||
}
|
||||
public ulong DecryptInvitationCodeToUlong(string invitationCode)
|
||||
{
|
||||
ulong number = 0;
|
||||
foreach (char c in invitationCode)
|
||||
{
|
||||
number *= baseLength;
|
||||
number += (ulong)chars.IndexOf(c);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
public string GetInvitationCodeByPlayFabID(string playFabID)
|
||||
{
|
||||
ulong code = Convert.ToUInt64(playFabID, 16);
|
||||
string invitationCode = EncryptUlongToInvitationCode(code);
|
||||
|
||||
return InsertFormat(invitationCode, 3, "-");
|
||||
}
|
||||
public string InsertFormat(string input, int interval, string value)
|
||||
{
|
||||
for (int i = interval; i < input.Length; i += interval + 1)
|
||||
input = input.Insert(i, value);
|
||||
return input;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 获得X天内登录过的活跃好友数量
|
||||
|
||||
public int GetActiveFriendCount(int xDays)
|
||||
{
|
||||
return FriendList.Count(data =>
|
||||
data.LastLogin is not null &&
|
||||
ZZTimeHelper.UtcNow() - data.LastLogin <= TimeSpan.FromDays(xDays));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 判断当前用户是否处于被邀请状态
|
||||
public async Task<string> IsFriendWaitingForAccept(string playfabID, string friendPlayfabID)
|
||||
{
|
||||
IsFriendWaitingForAcceptReq req = new IsFriendWaitingForAcceptReq()
|
||||
{
|
||||
PlayFabId = playfabID,
|
||||
FriendPlayFabId = friendPlayfabID
|
||||
};
|
||||
|
||||
return await customServerMgr.FriendRequest("IsFriendWaitingForAccept", req);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 后台操作
|
||||
List<FriendAddPanelBackendOperation> m_FriendAddPanelBackendOperationList = new List<FriendAddPanelBackendOperation>();
|
||||
public void StartInvite(string playfabID)
|
||||
{
|
||||
var backendOperation = new FriendAddPanelBackendOperation();
|
||||
m_FriendAddPanelBackendOperationList.Add(backendOperation);
|
||||
backendOperation.StartInvite(playfabID, OnInviteCallback);
|
||||
}
|
||||
public void OnInviteCallback(FriendAddPanelBackendOperation backendOperation, string playfabID, int code)
|
||||
{
|
||||
if (backendOperation != null)
|
||||
{
|
||||
backendOperation.Clear();
|
||||
m_FriendAddPanelBackendOperationList.Remove(backendOperation);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void Release()
|
||||
{
|
||||
linkCompletedDisposable?.Dispose();
|
||||
linkCompletedDisposable = null;
|
||||
|
||||
m_FriendAddPanelBackendOperationList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user