fix: align backend APIs and upload flow

This commit is contained in:
2026-09-11 18:18:59 +08:00
parent 32177a7293
commit 898cf5dba0
69 changed files with 1081 additions and 153 deletions
@@ -8,7 +8,7 @@ namespace MessageService.WebApi.Application.Conversation
public ConversationMapperConfig()
{
CreateMap<Domain.Entities.Conversation, ConversationResponse>()
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.ModificationTime))
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.ModificationTime ?? src.CreationTime))
;
}
}
@@ -31,6 +31,6 @@ namespace MessageService.WebApi.Application.Dtos
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; }
public DateTime DateTime { get; set; }
public DateTimeOffset DateTime { get; set; }
}
}
@@ -6,7 +6,7 @@ using MessageService.Infrastructure;
namespace MessageService.WebApi.Application.EventHandlers
{
public class ConversationAddHandler : IConsumer<GroupMemberJoinedEvent>,
IConsumer<FriendAddedEvent>
IConsumer<FriendAddedEvent>, IConsumer<GroupMemberLeftEvent>
{
private readonly IConversationReposity reposity;
@@ -21,6 +21,11 @@ namespace MessageService.WebApi.Application.EventHandlers
public async Task Consume(ConsumeContext<GroupMemberJoinedEvent> context)
{
var @event = context.Message;
var existing = await reposity.FindByTargetIdAsync(@event.GroupId);
if (existing.Any(x => x.UserId == @event.UserId && x.ChatType == Domain.Enums.ChatType.GROUP))
{
return;
}
reposity.Create(new Domain.Entities.Conversation(
userId: @event.UserId,
targetId: @event.GroupId,
@@ -32,12 +37,17 @@ namespace MessageService.WebApi.Application.EventHandlers
lastMessage: string.Empty
));
await messageDb.SaveChangesAsync();
await messageDb.SaveChangesAsync(context.CancellationToken);
}
public async Task Consume(ConsumeContext<FriendAddedEvent> context)
{
var @event = context.Message;
var existing = await reposity.FindByUserIdAsync(@event.OwnerId);
if (existing.Any(x => x.TargetId == @event.TargetId && x.ChatType == Domain.Enums.ChatType.PRIVATE))
{
return;
}
reposity.Create(new Domain.Entities.Conversation(
userId: @event.OwnerId,
targetId: @event.TargetId,
@@ -49,7 +59,18 @@ namespace MessageService.WebApi.Application.EventHandlers
lastMessage: string.Empty
));
await messageDb.SaveChangesAsync();
await messageDb.SaveChangesAsync(context.CancellationToken);
}
public async Task Consume(ConsumeContext<GroupMemberLeftEvent> context)
{
var conversations = await reposity.FindByTargetIdAsync(context.Message.GroupId);
foreach (var conversation in conversations.Where(x =>
x.UserId == context.Message.UserId && x.ChatType == Domain.Enums.ChatType.GROUP))
{
conversation.SoftDelete();
}
await messageDb.SaveChangesAsync(context.CancellationToken);
}
}
}
@@ -24,20 +24,24 @@ namespace MessageService.WebApi.Application.EventHandlers
{
var message = notification.Message;
if(message.ChatType == Domain.Enums.ChatType.PRIVATE)
var conversations = await reposity.FindByStreamKeyAsync(message.StreamKey);
foreach (var conversation in conversations)
{
var list = await reposity.FindByStreamKeyAsync(message.StreamKey);
var owner = list.First(x => x.UserId == message.SenderId);
var target = list.First(x => x.UserId == message.TargetId);
owner.Update(message.SequenceId, 0, message.Content.Fallback);
target.Update(target.LastReadSequenceId, target.UnreadCount + 1, message.Content.Fallback);
messageDb.Conversations.UpdateRange(owner,target);
await messageDb.SaveChangesAsync(cancellationToken);
conversation.UpdateLastMessage(message.Content.Fallback);
if (conversation.UserId == message.SenderId)
{
conversation.SetLastReadSequence(message.SequenceId);
}
else
{
conversation.IncrementUnread();
}
}
await endpoint.Publish(message.ToIntegrationEvent());
messageDb.Conversations.UpdateRange(conversations);
await messageDb.SaveChangesAsync(cancellationToken);
await endpoint.Publish(message.ToIntegrationEvent(), cancellationToken);
}
public async Task Handle(MessageWithdrawDomainEvent notification, CancellationToken cancellationToken)
@@ -52,13 +52,16 @@ namespace MessageService.WebApi.Application.Message
{
MessageType.Text => Domain.Entities.Message.BuildTxt(ctx, command.Text!, sequenceId),
MessageType.Image => Domain.Entities.Message.BuildImg(ctx, command.Url!,
command.Width ?? 0, command.Height ?? 0, command.Thumb!, sequenceId),
MessageType.Image => Domain.Entities.Message.BuildImg(ctx, command.Url,
command.Width ?? 0, command.Height ?? 0, command.Thumb!, sequenceId, command.FileId),
MessageType.Video => Domain.Entities.Message.BuildVideo(ctx, command.Url!,
command.Width ?? 0, command.Height ?? 0, command.Thumb!, sequenceId),
MessageType.Video => Domain.Entities.Message.BuildVideo(ctx, command.Url,
command.Width ?? 0, command.Height ?? 0, command.Thumb!, sequenceId, command.FileId),
MessageType.Voice => Domain.Entities.Message.BuildVoice(ctx, command.Url!, command.Duration ?? 0, sequenceId),
MessageType.Voice => Domain.Entities.Message.BuildVoice(ctx, command.Url, command.Duration ?? 0, sequenceId, command.FileId),
MessageType.File => Domain.Entities.Message.BuildFile(ctx, command.FileId!.Value, command.Url,
command.FileName!, command.FileSize!.Value, command.FileFormat!, sequenceId),
_ => null
};
@@ -109,6 +112,11 @@ namespace MessageService.WebApi.Application.Message
public async Task<Result<GetMessagesResponse>> GetMessagesAsync(GetMessageCommand command)
{
if (command.direction is not 0 and not 1 || command.limit is < 1 or > 100)
{
return Result.Fail<GetMessagesResponse>(ResultCode.PARAMETER_ERROR);
}
var conversation = await conversationReposity.FindByIdAsync(command.conversationId);
if(conversation is null || conversation.UserId != command.userId)
@@ -22,8 +22,12 @@ namespace MessageService.WebApi.Application.Message
public int? Height { get; init; }
public string? Thumb { get; init; }
public int? Duration { get; init; }
public Guid? FileId { get; init; }
public string? FileName { get; init; }
public long? FileSize { get; init; }
public string? FileFormat { get; init; }
public SendMsgCommand(Guid senderId, Guid targetId, ChatType chatType, MessageType msgType, Guid clientMsgId, Guid? quoteMessageId, Dictionary<string, string>? ext, string? text, string? url, int? width, int? height, string? thumb, int? duration)
public SendMsgCommand(Guid senderId, Guid targetId, ChatType chatType, MessageType msgType, Guid clientMsgId, Guid? quoteMessageId, Dictionary<string, string>? ext, string? text, string? url, int? width, int? height, string? thumb, int? duration, Guid? fileId, string? fileName, long? fileSize, string? fileFormat)
{
SenderId = senderId;
TargetId = targetId;
@@ -38,6 +42,10 @@ namespace MessageService.WebApi.Application.Message
Height = height;
Thumb = thumb;
Duration = duration;
FileId = fileId;
FileName = fileName;
FileSize = fileSize;
FileFormat = fileFormat;
}
}
}
@@ -23,6 +23,10 @@ namespace MessageService.WebApi.Controllers.Message
public int? Height { get; init; }
public string? Thumb { get; init; }
public int? Duration { get; init; }
public Guid? FileId { get; init; }
public string? FileName { get; init; }
public long? FileSize { get; init; }
public string? FileFormat { get; init; }
public SendMsgCommand ToCommand(Guid senderId)
{
@@ -39,7 +43,11 @@ namespace MessageService.WebApi.Controllers.Message
Width,
Height,
Thumb,
Duration
Duration,
FileId,
FileName,
FileSize,
FileFormat
);
}
}
@@ -63,9 +71,19 @@ namespace MessageService.WebApi.Controllers.Message
// 图片/视频/语音消息:url 必填
When(r => r.MsgType == MessageType.Image || r.MsgType == MessageType.Video
|| r.MsgType == MessageType.Voice || r.MsgType == MessageType.File, () =>
|| r.MsgType == MessageType.Voice, () =>
{
RuleFor(r => r.Url).NotEmpty().WithMessage("媒体消息的 url 字段不能为空");
RuleFor(r => r)
.Must(request => !string.IsNullOrWhiteSpace(request.Url) || request.FileId.HasValue)
.WithMessage("媒体消息必须提供 url 或 fileId");
});
When(r => r.MsgType == MessageType.File, () =>
{
RuleFor(r => r.FileId).NotNull().NotEmpty().WithMessage("文件消息的 fileId 字段不能为空");
RuleFor(r => r.FileName).NotEmpty().WithMessage("文件消息的 fileName 字段不能为空");
RuleFor(r => r.FileSize).NotNull().GreaterThanOrEqualTo(0).WithMessage("文件消息的 fileSize 字段不合法");
RuleFor(r => r.FileFormat).NotEmpty().WithMessage("文件消息的 fileFormat 字段不能为空");
});
}
}
+9 -2
View File
@@ -20,9 +20,16 @@ namespace MessageService.WebApi
services.AddScoped<ConversationService>();
services.AddScoped<SquenceService>();
services.AddScoped<IContactIntegrationService, ContactIntegrationService>();
services.AddHttpClient<IGroupMemberIntegrationService, GroupMemberIntegrationService>(c =>
services.AddHttpClient<IGroupMemberIntegrationService, GroupMemberIntegrationService>((sp, c) =>
{
c.BaseAddress = new Uri("http://im-group-service:8080/");
var configuration = sp.GetRequiredService<IConfiguration>();
c.BaseAddress = new Uri(configuration["InternalServices:GroupServiceBaseUrl"]
?? "http://im-group-service:8080/");
var internalApiKey = configuration["InternalApiKey"];
if (!string.IsNullOrWhiteSpace(internalApiKey))
{
c.DefaultRequestHeaders.Add("X-Internal-Api-Key", internalApiKey);
}
});
services.AddGrpcClient<ContactInternal.ContactInternalClient>((sp, o) =>
{
@@ -1,4 +1,8 @@
{
"InternalApiKey": "development-only-change-me",
"InternalServices": {
"GroupServiceBaseUrl": "http://localhost:5070/"
},
"Logging": {
"LogLevel": {
"Default": "Information",
+5 -1
View File
@@ -5,5 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"InternalApiKey": "",
"InternalServices": {
"GroupServiceBaseUrl": "http://im-group-service:8080/"
}
}