Files
ft/CodexOutput/upserver/B3BatchMigration/M2/C2d修改前快照/057-FriendService.cs.txt
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

462 lines
17 KiB
Plaintext
Raw 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 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();
}
}
}