Skip to content

Commit f4bcfe5

Browse files
committed
feat(friend): 实现好友增删查业务逻辑和消息处理器
- 实现添加好友、删除好友、好友列表的完整业务逻辑 - Social 进程内直接处理,非 Social 进程通过统一消息转发 - 新增三个消息处理器(ReqDeleteFriend/ReqInnerFriendByAdd/ReqInnerFriendByDelete) - 完善错误码映射和业务异常日志
1 parent 79e6459 commit f4bcfe5

4 files changed

Lines changed: 323 additions & 6 deletions

File tree

GameFrameX.Hotfix/Logic/Player/Friend/FriendComponentAgent.cs

Lines changed: 206 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717

1818
using GameFrameX.Apps.Player.Friend;
19+
using GameFrameX.Apps.Player.Friend.Entity;
20+
using GameFrameX.Apps.Player.Player.Entity;
1921
using GameFrameX.NetWork.RemoteMessaging.Contracts;
2022
using GameFrameX.NetWork.RemoteMessaging.Unified;
2123

@@ -86,6 +88,16 @@ public async Task OnAddFriend(INetWorkChannel netWorkChannel, ReqFriendByAdd req
8688
{
8789
var reqInnerAddFriend = MessageObjectPoolHelper.Get<ReqInnerFriendByAdd>();
8890
reqInnerAddFriend.PlayerId = request.PlayerId;
91+
if (string.Equals(GlobalSettings.CurrentSetting?.ServerType, GameServerConst.Social.Name, StringComparison.OrdinalIgnoreCase))
92+
{
93+
var innerResponse = new RespInnerFriendByAdd();
94+
await OnInnerAddFriend(netWorkChannel, reqInnerAddFriend, innerResponse);
95+
response.Success = innerResponse.Success;
96+
response.ErrorCode = innerResponse.ErrorCode;
97+
MessageObjectPoolHelper.Return(reqInnerAddFriend);
98+
return;
99+
}
100+
89101
try
90102
{
91103
var options = ServerSendOptions.Command(RpcTimeoutMilliseconds);
@@ -95,16 +107,19 @@ public async Task OnAddFriend(INetWorkChannel netWorkChannel, ReqFriendByAdd req
95107
if (result.IsSuccess && result.Response != null)
96108
{
97109
response.Success = result.Response.Success;
110+
response.ErrorCode = NormalizeBusinessErrorCode(result.Response.Success, result.Response.ErrorCode, request.PlayerId, "OnAddFriend");
98111
return;
99112
}
100113

101114
response.Success = false;
115+
response.ErrorCode = MapToBusinessErrorCode(result.StatusCode);
102116
LogHelper.Error("FriendComponentAgent.OnAddFriend 统一消息发送失败, StatusCode: {statusCode}, Error: {errorMessage}, TraceId: {traceId}",
103117
result.StatusCode, result.ErrorMessage, result.TraceId);
104118
}
105119
catch (Exception exception)
106120
{
107121
response.Success = false;
122+
response.ErrorCode = -2;
108123
LogHelper.Error(exception, "FriendComponentAgent.OnAddFriend 统一消息发送异常");
109124
}
110125
finally
@@ -113,24 +128,197 @@ public async Task OnAddFriend(INetWorkChannel netWorkChannel, ReqFriendByAdd req
113128
}
114129
}
115130

131+
/// <summary>
132+
/// 删除好友(通过统一消息发送器调用 Social 服务)。
133+
/// 删除好友为非幂等操作,不允许重试。
134+
/// </summary>
135+
/// <param name="netWorkChannel">网络通道</param>
136+
/// <param name="request">请求</param>
137+
/// <param name="response">响应</param>
138+
public async Task OnDeleteFriend(INetWorkChannel netWorkChannel, ReqDeleteFriend request, RespDeleteFriend response)
139+
{
140+
var reqInnerDeleteFriend = MessageObjectPoolHelper.Get<ReqInnerFriendByDelete>();
141+
reqInnerDeleteFriend.PlayerId = request.PlayerId;
142+
if (string.Equals(GlobalSettings.CurrentSetting?.ServerType, GameServerConst.Social.Name, StringComparison.OrdinalIgnoreCase))
143+
{
144+
var innerResponse = new RespInnerFriendByDelete();
145+
await OnInnerDeleteFriend(netWorkChannel, reqInnerDeleteFriend, innerResponse);
146+
response.Success = innerResponse.Success;
147+
response.ErrorCode = innerResponse.ErrorCode;
148+
MessageObjectPoolHelper.Return(reqInnerDeleteFriend);
149+
return;
150+
}
151+
152+
try
153+
{
154+
var options = ServerSendOptions.Command(RpcTimeoutMilliseconds);
155+
var result = await UnifiedMessageSenderHolder.Sender.SendToServerAsync<RespInnerFriendByDelete>(
156+
GameServerConst.Social.Name, reqInnerDeleteFriend, options);
157+
158+
if (result.IsSuccess && result.Response != null)
159+
{
160+
response.Success = result.Response.Success;
161+
response.ErrorCode = NormalizeBusinessErrorCode(result.Response.Success, result.Response.ErrorCode, request.PlayerId, "OnDeleteFriend");
162+
return;
163+
}
164+
165+
response.Success = false;
166+
response.ErrorCode = MapToBusinessErrorCode(result.StatusCode);
167+
LogHelper.Error("FriendComponentAgent.OnDeleteFriend 统一消息发送失败, StatusCode: {statusCode}, Error: {errorMessage}, TraceId: {traceId}",
168+
result.StatusCode, result.ErrorMessage, result.TraceId);
169+
}
170+
catch (Exception exception)
171+
{
172+
response.Success = false;
173+
response.ErrorCode = -2;
174+
LogHelper.Error(exception, "FriendComponentAgent.OnDeleteFriend 统一消息发送异常");
175+
}
176+
finally
177+
{
178+
MessageObjectPoolHelper.Return(reqInnerDeleteFriend);
179+
}
180+
}
181+
182+
/// <summary>
183+
/// Social 进程内的添加好友处理。
184+
/// 将好友关系写入数据库。
185+
/// </summary>
186+
public async Task OnInnerAddFriend(INetWorkChannel netWorkChannel, ReqInnerFriendByAdd request, RespInnerFriendByAdd response)
187+
{
188+
var ownerPlayerId = ActorId;
189+
var targetPlayerId = request.PlayerId;
190+
191+
// 参数校验
192+
if (ownerPlayerId <= 0 || targetPlayerId <= 0)
193+
{
194+
response.Success = false;
195+
response.ErrorCode = -100;
196+
return;
197+
}
198+
199+
// 不能加自己为好友
200+
if (ownerPlayerId == targetPlayerId)
201+
{
202+
response.Success = false;
203+
response.ErrorCode = -101;
204+
return;
205+
}
206+
207+
// 确认目标玩家存在
208+
var targetPlayer = await GameDb.FindAsync<PlayerState>(targetPlayerId);
209+
if (targetPlayer == null)
210+
{
211+
response.Success = false;
212+
response.ErrorCode = -102;
213+
return;
214+
}
215+
216+
// 构建关系(确保 PlayerIdA < PlayerIdB 保证唯一性)
217+
long playerIdA = Math.Min(ownerPlayerId, targetPlayerId);
218+
long playerIdB = Math.Max(ownerPlayerId, targetPlayerId);
219+
220+
// 检查是否已存在好友关系
221+
var existingRelation = await GameDb.FindAsync<FriendRelationState>(
222+
m => m.PlayerIdA == playerIdA && m.PlayerIdB == playerIdB && m.Status == 0, false);
223+
224+
if (existingRelation != null)
225+
{
226+
response.Success = false;
227+
response.ErrorCode = -103; // 已是好友
228+
return;
229+
}
230+
231+
// 插入新的好友关系
232+
var newRelation = new FriendRelationState
233+
{
234+
Id = ActorIdGenerator.GetActorId(GlobalConst.ActorTypePlayer),
235+
PlayerIdA = playerIdA,
236+
PlayerIdB = playerIdB,
237+
CreateTime = DateTime.UtcNow,
238+
CreatedBy = ownerPlayerId,
239+
Status = 0
240+
};
241+
242+
await GameDb.AddOrUpdateAsync(newRelation);
243+
response.Success = true;
244+
response.ErrorCode = 0;
245+
}
246+
116247
/// <summary>
117248
/// Social 进程内的好友列表处理。
249+
/// 从数据库查询所有有效好友关系,并填充好友详情。
118250
/// </summary>
119251
/// <param name="netWorkChannel">网络通道</param>
120252
/// <param name="request">请求</param>
121253
/// <param name="response">响应</param>
122254
public async Task OnInnerFriendList(INetWorkChannel netWorkChannel, ReqInnerFriendList request, RespInnerFriendList response)
123255
{
124-
response.Friends = new List<FriendInfo>
256+
var ownerPlayerId = request.PlayerId > 0 ? request.PlayerId : ActorId;
257+
258+
// 查询所有包含自己的有效好友关系
259+
var relations = await GameDb.FindListAsync<FriendRelationState>(
260+
m => (m.PlayerIdA == ownerPlayerId || m.PlayerIdB == ownerPlayerId) && m.Status == 0);
261+
262+
var friends = new List<FriendInfo>();
263+
foreach (var relation in relations)
125264
{
126-
new FriendInfo
265+
// 找出对方玩家的ID
266+
long friendPlayerId = relation.PlayerIdA == ownerPlayerId ? relation.PlayerIdB : relation.PlayerIdA;
267+
268+
// 查找对方玩家详情
269+
var friendPlayer = await GameDb.FindAsync<PlayerState>(friendPlayerId);
270+
if (friendPlayer != null)
127271
{
128-
PlayerId = request.PlayerId <= 0 ? 1 : request.PlayerId,
129-
PlayerName = "SocialLocalFriend"
272+
friends.Add(new FriendInfo
273+
{
274+
PlayerId = friendPlayer.Id,
275+
PlayerName = friendPlayer.Name
276+
});
130277
}
131-
};
278+
}
279+
280+
response.Friends = friends;
281+
response.ErrorCode = 0;
282+
}
283+
284+
/// <summary>
285+
/// Social 进程内的删除好友处理。
286+
/// 软删除好友关系(设置 Status = 1)。
287+
/// </summary>
288+
public async Task OnInnerDeleteFriend(INetWorkChannel netWorkChannel, ReqInnerFriendByDelete request, RespInnerFriendByDelete response)
289+
{
290+
var ownerPlayerId = ActorId;
291+
var targetPlayerId = request.PlayerId;
292+
293+
// 参数校验
294+
if (ownerPlayerId <= 0 || targetPlayerId <= 0)
295+
{
296+
response.Success = false;
297+
response.ErrorCode = -100;
298+
return;
299+
}
300+
301+
// 构建关系键
302+
long playerIdA = Math.Min(ownerPlayerId, targetPlayerId);
303+
long playerIdB = Math.Max(ownerPlayerId, targetPlayerId);
304+
305+
// 查找好友关系
306+
var relation = await GameDb.FindAsync<FriendRelationState>(
307+
m => m.PlayerIdA == playerIdA && m.PlayerIdB == playerIdB && m.Status == 0, false);
308+
309+
if (relation == null)
310+
{
311+
response.Success = false;
312+
response.ErrorCode = -104; // 好友关系不存在
313+
return;
314+
}
315+
316+
// 软删除
317+
relation.Status = 1;
318+
await GameDb.AddOrUpdateAsync(relation);
319+
320+
response.Success = true;
132321
response.ErrorCode = 0;
133-
await Task.CompletedTask;
134322
}
135323

136324
/// <summary>
@@ -162,4 +350,16 @@ private static int MapToBusinessErrorCode(RemoteStatusCode statusCode)
162350
return -99;
163351
}
164352
}
353+
354+
private static int NormalizeBusinessErrorCode(bool success, int errorCode, long requestPlayerId, string actionName)
355+
{
356+
if (success || errorCode != 0)
357+
{
358+
return errorCode;
359+
}
360+
361+
LogHelper.Error("FriendComponentAgent.{actionName} 返回语义异常: Success=false 且 ErrorCode=0, PlayerId={playerId}",
362+
actionName, requestPlayerId);
363+
return -101;
364+
}
165365
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// ==========================================================================================
2+
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+
// 均受中华人民共和国及相关国际法律法规保护。
5+
// are protected by the laws of the People's Republic of China and relevant international regulations.
6+
// 使用本项目须严格遵守相应法律法规及开源许可证之规定。
7+
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
8+
// 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
9+
// This project is dual-licensed under the MIT License and Apache License 2.0,
10+
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
11+
// please refer to the LICENSE file in the root directory of the source code for the full license text.
12+
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
13+
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
14+
// 侵犯他人合法权益等法律法规所禁止的行为!
15+
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
16+
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
17+
// Any legal disputes and liabilities arising from secondary development based on this project
18+
// 本项目组织与贡献者概不承担。
19+
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
20+
// GitHub 仓库:https://github.com/GameFrameX
21+
// GitHub Repository: https://github.com/GameFrameX
22+
// Gitee 仓库:https://gitee.com/GameFrameX
23+
// Gitee Repository: https://gitee.com/GameFrameX
24+
// CNB 仓库:https://cnb.cool/GameFrameX
25+
// CNB Repository: https://cnb.cool/GameFrameX
26+
// 官方文档:https://gameframex.doc.alianblank.com/
27+
// Official Documentation: https://gameframex.doc.alianblank.com/
28+
// ==========================================================================================
29+
30+
namespace GameFrameX.Hotfix.Logic.Player.Friend;
31+
32+
[MessageMapping(typeof(ReqDeleteFriend))]
33+
internal sealed class ReqDeleteFriendHandler : GlobalRpcComponentHandler<FriendComponentAgent, ReqDeleteFriend, RespDeleteFriend>
34+
{
35+
protected override async Task ActionAsync(ReqDeleteFriend request, RespDeleteFriend response)
36+
{
37+
await ComponentAgent.OnDeleteFriend(NetWorkChannel, request, response);
38+
}
39+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// ==========================================================================================
2+
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+
// 均受中华人民共和国及相关国际法律法规保护。
5+
// are protected by the laws of the People's Republic of China and relevant international regulations.
6+
// 使用本项目须严格遵守相应法律法规及开源许可证之规定。
7+
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
8+
// 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
9+
// This project is dual-licensed under the MIT License and Apache License 2.0,
10+
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
11+
// please refer to the LICENSE file in the root directory of the source code for the full license text.
12+
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
13+
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
14+
// 侵犯他人合法权益等法律法规所禁止的行为!
15+
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
16+
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
17+
// Any legal disputes and liabilities arising from secondary development based on this project
18+
// 本项目组织与贡献者概不承担。
19+
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
20+
// GitHub 仓库:https://github.com/GameFrameX
21+
// GitHub Repository: https://github.com/GameFrameX
22+
// Gitee 仓库:https://gitee.com/GameFrameX
23+
// Gitee Repository: https://gitee.com/GameFrameX
24+
// CNB 仓库:https://cnb.cool/GameFrameX
25+
// CNB Repository: https://cnb.cool/GameFrameX
26+
// 官方文档:https://gameframex.doc.alianblank.com/
27+
// Official Documentation: https://gameframex.doc.alianblank.com/
28+
// ==========================================================================================
29+
30+
namespace GameFrameX.Hotfix.Logic.Player.Friend;
31+
32+
[MessageMapping(typeof(ReqInnerFriendByAdd))]
33+
internal sealed class ReqInnerFriendByAddHandler : GlobalRpcComponentHandler<FriendComponentAgent, ReqInnerFriendByAdd, RespInnerFriendByAdd>
34+
{
35+
protected override async Task ActionAsync(ReqInnerFriendByAdd request, RespInnerFriendByAdd response)
36+
{
37+
await ComponentAgent.OnInnerAddFriend(NetWorkChannel, request, response);
38+
}
39+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// ==========================================================================================
2+
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+
// 均受中华人民共和国及相关国际法律法规保护。
5+
// are protected by the laws of the People's Republic of China and relevant international regulations.
6+
// 使用本项目须严格遵守相应法律法规及开源许可证之规定。
7+
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
8+
// 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
9+
// This project is dual-licensed under the MIT License and Apache License 2.0,
10+
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
11+
// please refer to the LICENSE file in the root directory of the source code for the full license text.
12+
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
13+
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
14+
// 侵犯他人合法权益等法律法规所禁止的行为!
15+
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
16+
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
17+
// Any legal disputes and liabilities arising from secondary development based on this project
18+
// 本项目组织与贡献者概不承担。
19+
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
20+
// GitHub 仓库:https://github.com/GameFrameX
21+
// GitHub Repository: https://github.com/GameFrameX
22+
// Gitee 仓库:https://gitee.com/GameFrameX
23+
// Gitee Repository: https://gitee.com/GameFrameX
24+
// CNB 仓库:https://cnb.cool/GameFrameX
25+
// CNB Repository: https://cnb.cool/GameFrameX
26+
// 官方文档:https://gameframex.doc.alianblank.com/
27+
// Official Documentation: https://gameframex.doc.alianblank.com/
28+
// ==========================================================================================
29+
30+
namespace GameFrameX.Hotfix.Logic.Player.Friend;
31+
32+
[MessageMapping(typeof(ReqInnerFriendByDelete))]
33+
internal sealed class ReqInnerFriendByDeleteHandler : GlobalRpcComponentHandler<FriendComponentAgent, ReqInnerFriendByDelete, RespInnerFriendByDelete>
34+
{
35+
protected override async Task ActionAsync(ReqInnerFriendByDelete request, RespInnerFriendByDelete response)
36+
{
37+
await ComponentAgent.OnInnerDeleteFriend(NetWorkChannel, request, response);
38+
}
39+
}

0 commit comments

Comments
 (0)