Merge branch 'master' of https://gitea.nxsir.cn/nanxun/IM_NEW
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
using GroupService.Domain.Events;
|
||||
using GroupService.WebApi.Application.GroupRequest;
|
||||
using GroupService.WebApi.Application.GroupMember;
|
||||
using IM.Commons;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
@@ -12,20 +12,19 @@ namespace GroupService.WebApi.Application.EventHandler
|
||||
, INotificationHandler<GroupInvitationCreateDomainEvent>
|
||||
{
|
||||
private readonly IPublishEndpoint endpoint;
|
||||
private readonly GroupRequestService requestService;
|
||||
private readonly GroupMemberService memberService;
|
||||
|
||||
public GroupInvitationEventHandler(IPublishEndpoint endpoint, GroupRequestService requestService)
|
||||
public GroupInvitationEventHandler(IPublishEndpoint endpoint, GroupMemberService memberService)
|
||||
{
|
||||
this.endpoint = endpoint;
|
||||
this.requestService = requestService;
|
||||
this.memberService = memberService;
|
||||
}
|
||||
|
||||
public async Task Handle(GroupInvitationAcceptDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var invitation = notification.Invitation;
|
||||
var res = await requestService.CreateAsync(invitation.GroupId, invitation.UserId,
|
||||
$"邀请入群"
|
||||
);
|
||||
// 邀请接受 → 直接加入群聊,不走入群申请审批流程
|
||||
var res = await memberService.CreateAsync(invitation.GroupId, invitation.UserId);
|
||||
|
||||
await endpoint.Publish(new GroupInvitationAcceptEvent(invitation.Id, invitation.UserId, invitation.UserProfile.NickName, invitation.UserProfile.Avatar
|
||||
, invitation.GroupId, invitation.GroupProfile.GroupName, invitation.GroupProfile.Avatar, invitation.OperatorId
|
||||
@@ -34,7 +33,6 @@ namespace GroupService.WebApi.Application.EventHandler
|
||||
|
||||
if (!res.Succeeded)
|
||||
{
|
||||
|
||||
throw new EventHandlerException(res.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using GroupService.Domain.Events;
|
||||
using GroupService.Infrastructure;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
@@ -8,18 +9,25 @@ namespace GroupService.WebApi.Application.EventHandler
|
||||
public class GroupMemberJoinedHandler : INotificationHandler<GroupMemberJoinedDomainEvent>
|
||||
{
|
||||
private readonly IPublishEndpoint endpoint;
|
||||
private readonly GroupDbContext db;
|
||||
|
||||
public GroupMemberJoinedHandler(IPublishEndpoint endpoint)
|
||||
public GroupMemberJoinedHandler(IPublishEndpoint endpoint, GroupDbContext db)
|
||||
{
|
||||
this.endpoint = endpoint;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async Task Handle(GroupMemberJoinedDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var member = notification.Member;
|
||||
// 用 FindAsync 而非 FirstOrDefaultAsync:群可能刚创建尚未提交到数据库,FindAsync 先查内存跟踪器
|
||||
var group = await db.Set<Domain.Entities.Group>().FindAsync(member.GroupId);
|
||||
var groupName = group?.Name ?? "群聊";
|
||||
var groupAvatar = group?.Avatar;
|
||||
|
||||
await endpoint.Publish(new GroupMemberJoinedEvent(member.Id,
|
||||
member.UserId, member.GroupId, member.GroupNickName, member.Avatar,
|
||||
member.Role.ToString()), cancellationToken);
|
||||
member.Role.ToString(), groupName, groupAvatar), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ namespace GroupService.WebApi.Application.Group
|
||||
public class GroupService
|
||||
{
|
||||
private readonly IGroupReposity reposity;
|
||||
private readonly IGroupMemberReposity memberReposity;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public GroupService(IGroupReposity reposity, IMapper mapper)
|
||||
public GroupService(IGroupReposity reposity, IGroupMemberReposity memberReposity, IMapper mapper)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.memberReposity = memberReposity;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@@ -39,5 +41,19 @@ namespace GroupService.WebApi.Application.Group
|
||||
|
||||
return Result<GroupResponse>.Success(mapper.Map<GroupResponse>(group));
|
||||
}
|
||||
|
||||
public async Task<Result<GroupResponse>> UpdateAsync(GroupUpdateCommand command)
|
||||
{
|
||||
var group = await reposity.FindByIdAsync(command.GroupId);
|
||||
if(group is null)
|
||||
return Result.Fail<GroupResponse>(ResultCode.GROUP_NOT_FOUND);
|
||||
|
||||
var isAdmin = await memberReposity.CheckMemberAdminAsync(group.Id ,command.UserId);
|
||||
if (!isAdmin)
|
||||
return Result.Fail<GroupResponse>(ResultCode.ADMIN_PERMISSION_DENIED);
|
||||
|
||||
group.Update(command.GroupName, null, command.Description, command.Avatar);
|
||||
return Result.Success(mapper.Map<GroupResponse>(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace GroupService.WebApi.Application.Group
|
||||
{
|
||||
public record GroupUpdateCommand(Guid UserId, Guid GroupId, string? Avatar, string? GroupName, string? Description);
|
||||
}
|
||||
@@ -39,6 +39,13 @@ namespace GroupService.WebApi.Application.GroupInvitation
|
||||
return Result.Fail<GroupInvitationResponse>(ResultCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
// 检查是否已存在对该用户的未处理邀请,避免 Duplicate entry 报错
|
||||
var existing = await reposity.FindByGroupIdAndUserIdAsync(groupId, userId);
|
||||
if (existing != null)
|
||||
{
|
||||
return Result.Success(mapper.Map<GroupInvitationResponse>(existing));
|
||||
}
|
||||
|
||||
var group = await groupReposity.FindByIdAsync(groupId);
|
||||
|
||||
var userProfile = new UserProfile()
|
||||
@@ -66,6 +73,54 @@ namespace GroupService.WebApi.Application.GroupInvitation
|
||||
return Result.Success(mapper.Map<GroupInvitationResponse>(invitation));
|
||||
}
|
||||
|
||||
public async Task<Result<object>> CreateBatchAsync(Guid operatorId, List<Guid> userIds, Guid groupId)
|
||||
{
|
||||
var group = await groupReposity.FindByIdAsync(groupId);
|
||||
if (group is null)
|
||||
return Result.Fail(ResultCode.GROUP_NOT_FOUND);
|
||||
|
||||
// 校验操作者是否有权限(群成员/管理员/群主)
|
||||
var operatorMember = await memberReposity.FindOneByGroupIdAndUserIdAsync(groupId, operatorId);
|
||||
if (operatorMember == null)
|
||||
return Result.Fail(ResultCode.PERMISSION_DENIED);
|
||||
|
||||
var operatorInfo = await idService.FindUserByIdAsync(operatorId);
|
||||
var operatorProfile = new UserProfile()
|
||||
{
|
||||
Avatar = operatorInfo.Data?.Avatar,
|
||||
NickName = operatorInfo.Data?.NickName ?? string.Empty
|
||||
};
|
||||
|
||||
var groupProfile = new GroupProfile()
|
||||
{
|
||||
Avatar = group.Avatar,
|
||||
GroupName = group.Name
|
||||
};
|
||||
|
||||
// 批量创建邀请,跳过已存在未处理邀请的用户
|
||||
foreach (var userId in userIds)
|
||||
{
|
||||
var existing = await reposity.FindByGroupIdAndUserIdAsync(groupId, userId);
|
||||
if (existing != null)
|
||||
continue;
|
||||
|
||||
var userRes = await idService.FindUserByIdAsync(userId);
|
||||
if (!userRes.Succeeded)
|
||||
continue;
|
||||
|
||||
var userProfile = new UserProfile()
|
||||
{
|
||||
Avatar = userRes.Data?.Avatar,
|
||||
NickName = userRes.Data?.NickName ?? string.Empty
|
||||
};
|
||||
|
||||
var invitation = new Domain.Entities.GroupInvitation(
|
||||
groupId, groupProfile, userId, userProfile, operatorId, operatorProfile);
|
||||
reposity.Create(invitation);
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
public async Task<Result<object>> HandleAsync(GroupInvitationHandleCommand command)
|
||||
{
|
||||
var invitation = await reposity.FindByIdAsync(command.InvitationId);
|
||||
|
||||
@@ -96,6 +96,11 @@ namespace GroupService.WebApi.Application.GroupRequest
|
||||
|
||||
return Result.Success(mapper.Map<GroupRequestResponse>(request));
|
||||
}
|
||||
public async Task<Result<List<GroupRequestResponse>>> GetListAsync(Guid userId)
|
||||
{
|
||||
var list = await reposity.FindByUserIdAsync(userId);
|
||||
return Result.Success(mapper.Map<List<GroupRequestResponse>>(list.ToList()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,35 @@ namespace GroupService.WebApi.Application.IntegrationServices
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async Task<Result<List<UserInfoDto>>> FindByIdsAsync(List<Guid> ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetUserListRequest();
|
||||
request.UserIds.AddRange(ids.Select(x => x.ToString()));
|
||||
var response = await client.GetUserListAsyncAsync(request);
|
||||
var users = response.Users.Select(x => new UserInfoDto
|
||||
{
|
||||
Avatar = x.Avatar,
|
||||
CreationTime = x.CreationTime.ToDateTimeOffset(),
|
||||
Deletion = x.Deletion?.ToDateTimeOffset(),
|
||||
Description = x.Description,
|
||||
Email = x.Email,
|
||||
Id = Guid.Parse(x.Id),
|
||||
NickName = x.NickName,
|
||||
Phone = x.Phone,
|
||||
Region = x.Region,
|
||||
UserName = x.UserName
|
||||
}).ToList();
|
||||
|
||||
return Result.Success(users);
|
||||
}catch(RpcException e)
|
||||
{
|
||||
return Result.Fail<List<UserInfoDto>>(ResultCode.USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<Result<UserInfoDto>> FindUserByIdAsync(Guid id)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace GroupService.WebApi.Application.IntegrationServices
|
||||
public interface IIdentityIntegrationService
|
||||
{
|
||||
Task<Result<UserInfoDto>> FindUserByIdAsync(Guid id);
|
||||
Task<Result<List<UserInfoDto>>> FindByIdsAsync(List<Guid> ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace GroupService.WebApi.Controllers.Group
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetAllAsync(Guid.Parse(userId)));
|
||||
}
|
||||
[HttpGet("~/api/[controller]/{userId}")]
|
||||
[HttpGet]
|
||||
[ProducesDefaultResponseType(typeof(Result<GroupResponse>))]
|
||||
public async Task<IActionResult> GetOne([FromRoute] Guid userId)
|
||||
public async Task<IActionResult> GetOne(Guid groupId)
|
||||
{
|
||||
return Ok(await service.GetByIdAsync(userId));
|
||||
return Ok(await service.GetByIdAsync(groupId));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -42,5 +42,12 @@ namespace GroupService.WebApi.Controllers.Group
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.CreateAsync(new GroupCreateCommand(Guid.Parse(userId), request.Name)));
|
||||
}
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(GroupDbContext))]
|
||||
public async Task<IActionResult> Update(GroupUpdateRequest request)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.UpdateAsync(new GroupUpdateCommand(Guid.Parse(userId), request.GroupId, request.Avatar, request.GroupName, request.Description)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GroupService.WebApi.Controllers.Group
|
||||
{
|
||||
public class GroupUpdateRequest
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public string? GroupName { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
public class GroupUpDateRequestValidator : AbstractValidator<GroupUpdateRequest>
|
||||
{
|
||||
public GroupUpDateRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.GroupId)
|
||||
.NotNull()
|
||||
.NotEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,5 +41,12 @@ namespace GroupService.WebApi.Controllers.GroupRequest
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetByIdAsync(id, Guid.Parse(userId)));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetListAsync(Guid.Parse(userId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY IM_API_NEW.sln ./
|
||||
|
||||
COPY GroupService.WebApi/GroupService.WebApi.csproj GroupService.WebApi/
|
||||
COPY GroupService.Domain/GroupService.Domain.csproj GroupService.Domain/
|
||||
COPY GroupService.Infrastructure/GroupService.Infrastructure.csproj GroupService.Infrastructure/
|
||||
COPY DomainCommons/IM.DomainCommons.csproj DomainCommons/
|
||||
COPY Infrastructure/IM.Infrastructure.csproj Infrastructure/
|
||||
COPY IM.ASPNETCore/IM.ASPNETCore.csproj IM.ASPNETCore/
|
||||
COPY IM.Commons/IM.Commons.csproj IM.Commons/
|
||||
COPY IM.InitCommon/IM.InitCommon.csproj IM.InitCommon/
|
||||
COPY IM.Jwt/IM.Jwt.csproj IM.Jwt/
|
||||
COPY IM.Protocols/IM.Protocols.csproj IM.Protocols/
|
||||
|
||||
RUN dotnet restore GroupService.WebApi/GroupService.WebApi.csproj
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN find . -type d \( -name obj -o -name bin \) -prune -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
RUN dotnet publish GroupService.WebApi/GroupService.WebApi.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
-o /app/publish
|
||||
|
||||
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
Reference in New Issue
Block a user