添加项目文件。

This commit is contained in:
2026-04-30 21:08:28 +08:00
parent c60f5fe117
commit cc017b6495
327 changed files with 12860 additions and 0 deletions
@@ -0,0 +1,14 @@
using AutoMapper;
using MessageService.WebApi.Application.Dtos;
namespace MessageService.WebApi.Application.Conversation
{
public class ConversationMapperConfig : Profile
{
public ConversationMapperConfig()
{
CreateMap<Domain.Entities.Conversation, ConversationResponse>()
;
}
}
}
@@ -0,0 +1,43 @@
using AutoMapper;
using IM.Commons;
using MessageService.Domain.IReposities;
using MessageService.WebApi.Application.Dtos;
namespace MessageService.WebApi.Application.Conversation
{
public class ConversationService
{
private readonly IConversationReposity reposity;
private readonly IMapper mapper;
public ConversationService(IConversationReposity reposity, IMapper mapper)
{
this.reposity = reposity;
this.mapper = mapper;
}
public async Task<Result<List<ConversationResponse>>> GetByOwnerIdAsync(Guid userId)
{
var list = await reposity.FindByUserIdAsync(userId);
return Result.Success(mapper.Map<List<ConversationResponse>>(list.ToList()));
}
public async Task<Result<ConversationResponse>> GetByIdAsync(Guid id, Guid userId)
{
var conversation = await reposity.FindByIdAsync(id);
if (conversation is null || conversation.UserId != userId)
{
return Result.Fail<ConversationResponse>(ResultCode.CONVERSATION_NOT_FOUND);
}
return Result.Success(mapper.Map<ConversationResponse>(conversation));
}
public async Task<Result<List<string>>> GetStreamkeysAsync(Guid userId)
{
var list = await reposity.FindAllStreamKeyAsync(userId);
return Result.Success(list.ToList());
}
}
}
@@ -0,0 +1,35 @@
using MessageService.Domain.Enums;
namespace MessageService.WebApi.Application.Dtos
{
public class ConversationResponse
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
/// <summary>
/// 对方ID(群聊为群聊ID,单聊为单聊ID)
/// </summary>
public Guid TargetId { get; set; }
public string TargetAvatar { get; set; }
public string TargetName { get; set; }
/// <summary>
/// 最后一条未读消息ID
/// </summary>
public long? LastReadSequenceId { get; set; }
/// <summary>
/// 未读消息数
/// </summary>
public int UnreadCount { get; set; }
public ChatType ChatType { get; set; }
/// <summary>
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; }
}
}
@@ -0,0 +1,36 @@
using MessageService.Domain.Enums;
namespace MessageService.WebApi.Application.Dtos
{
public record MessageResponse
{
public Guid Id { get; init; }
public Guid ClientMsgId { get; init; }
public ChatType ChatType { get; init; }
public MessageType MsgType { get; init; }
public Guid SenderId { get; init; }
public Guid TargetId { get; init; }
public MessageState State { get; init; }
public string StreamKey { get; init; }
public long SequenceId { get; init; }
public DateTimeOffset CreationTime { get; init; }
// 关键:展开 Content
public MessageContentResponse Content { get; init; }
}
public record MessageContentResponse(
string Fallback,
object Body, // 已经是反序列化后的具体对象
Dictionary<string, string> Ext,
QuoteInfoResponse? Quote
);
public record QuoteInfoResponse(
Guid MessageId,
Guid SenderId,
string SenderName,
MessageType MessageType,
string Preview
);
}
@@ -0,0 +1,55 @@
using IM.Commons.IntegrationEvents;
using MassTransit;
using MessageService.Domain.IReposities;
using MessageService.Infrastructure;
namespace MessageService.WebApi.Application.EventHandlers
{
public class ConversationAddHandler : IConsumer<GroupMemberJoinedEvent>,
IConsumer<FriendAddedEvent>
{
private readonly IConversationReposity reposity;
private readonly MessageDbContext messageDb;
public ConversationAddHandler(IConversationReposity reposity, MessageDbContext messageDb)
{
this.reposity = reposity;
this.messageDb = messageDb;
}
public async Task Consume(ConsumeContext<GroupMemberJoinedEvent> context)
{
var @event = context.Message;
reposity.Create(new Domain.Entities.Conversation(
userId: @event.UserId,
targetId: @event.GroupId,
targetAvatar: @event.Avatar,
targetName: @event.GroupNickName,
lastReadSequenceId: null,
unreadCount:0,
chatType: Domain.Enums.ChatType.GROUP,
lastMessage: string.Empty
));
await messageDb.SaveChangesAsync();
}
public async Task Consume(ConsumeContext<FriendAddedEvent> context)
{
var @event = context.Message;
reposity.Create(new Domain.Entities.Conversation(
userId: @event.OwnerId,
targetId: @event.TargetId,
targetAvatar: @event.TargetAvatar,
targetName: @event.TargetNickName,
lastReadSequenceId: null,
unreadCount: 0,
chatType: Domain.Enums.ChatType.PRIVATE,
lastMessage: string.Empty
));
await messageDb.SaveChangesAsync();
}
}
}
@@ -0,0 +1,49 @@
using IM.Commons.IntegrationEvents;
using MassTransit;
using MediatR;
using MessageService.Domain.Events;
using MessageService.Domain.IReposities;
using MessageService.Infrastructure;
namespace MessageService.WebApi.Application.EventHandlers
{
public class MessageHandler : INotificationHandler<MessageCreatedDomainEvent>, INotificationHandler<MessageWithdrawDomainEvent>
{
private readonly IPublishEndpoint endpoint;
private readonly IConversationReposity reposity;
private readonly MessageDbContext messageDb;
public MessageHandler(IPublishEndpoint endpoint, IConversationReposity reposity, MessageDbContext messageDb)
{
this.endpoint = endpoint;
this.reposity = reposity;
this.messageDb = messageDb;
}
public async Task Handle(MessageCreatedDomainEvent notification, CancellationToken cancellationToken)
{
var message = notification.Message;
if(message.ChatType == Domain.Enums.ChatType.PRIVATE)
{
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);
}
await endpoint.Publish(message.ToIntegrationEvent());
}
public async Task Handle(MessageWithdrawDomainEvent notification, CancellationToken cancellationToken)
{
var message = notification.Message;
await endpoint.Publish(new MsgWithdrawEvent(message.Id, message.State.ToString(), message.StreamKey), cancellationToken);
}
}
}
@@ -0,0 +1,30 @@
using IM.Commons.IntegrationEvents;
using MassTransit;
using MessageService.Domain.IReposities;
using MessageService.Infrastructure;
namespace MessageService.WebApi.Application.EventHandlers
{
public class UserProfileUpdateHandler : IConsumer<UserProfileUpdateEvent>
{
private readonly MessageDbContext db;
private readonly IConversationReposity reposity;
public UserProfileUpdateHandler(MessageDbContext db, IConversationReposity reposity)
{
this.db = db;
this.reposity = reposity;
}
public async Task Consume(ConsumeContext<UserProfileUpdateEvent> context)
{
var @event = context.Message;
var conversations = await reposity.FindByTargetIdAsync(@event.UserId);
foreach (var conversation in conversations)
{
conversation.UpdateProfile(@event.NickName, @event.Avatar);
}
await db.SaveChangesAsync();
}
}
}
@@ -0,0 +1,27 @@
using IM.Commons;
using IM.Protocols.Grpc.Contact;
namespace MessageService.WebApi.Application.IntegrationServices
{
public class ContactIntegrationService : IContactIntegrationService
{
private readonly ContactInternal.ContactInternalClient client;
public ContactIntegrationService(ContactInternal.ContactInternalClient client)
{
this.client = client;
}
public async Task<bool> CheckContactAsync(Guid ownerId, Guid targetId)
{
var req = new CheckFriendshipRequest()
{
OwnerId = ownerId.ToString(),
TargetId = targetId.ToString(),
};
var res = await client.CheckFriendshipAsync(req);
return res.Checked;
}
}
}
@@ -0,0 +1,22 @@
using IM.Commons;
namespace MessageService.WebApi.Application.IntegrationServices
{
public class GroupMemberIntegrationService : IGroupMemberIntegrationService
{
private readonly HttpClient http;
public async Task<bool> CheckGroupMemberAsync(Guid userId, Guid groupId)
{
var result = await http.GetFromJsonAsync<Result<bool>>(
$"api/groupmember/checkmember?userId={userId}&groupId={groupId}");
if (!result.Succeeded)
{
return false;
}
return result.Data;
}
}
}
@@ -0,0 +1,7 @@
namespace MessageService.WebApi.Application.IntegrationServices
{
public interface IContactIntegrationService
{
Task<bool> CheckContactAsync(Guid ownerId, Guid targetId);
}
}
@@ -0,0 +1,7 @@
namespace MessageService.WebApi.Application.IntegrationServices
{
public interface IGroupMemberIntegrationService
{
Task<bool> CheckGroupMemberAsync(Guid userId, Guid groupId);
}
}
@@ -0,0 +1,32 @@
using AutoMapper;
using MessageService.Domain.KeyObjects;
using MessageService.WebApi.Application.Dtos;
namespace MessageService.WebApi.Application.Message
{
public class MessageMapperConfig : Profile
{
public MessageMapperConfig()
{
// 1. 配置 QuoteInfo -> QuoteInfoResponse
CreateMap<QuoteInfo, QuoteInfoResponse>();
// 2. 配置 MessageContent -> MessageContentResponse
CreateMap<MessageContent, MessageContentResponse>()
// 关键点:Body 是 object,由于我们在实体里写了 Body 计算属性
// AutoMapper 默认会识别到同名的 Body 属性并进行映射
// 如果你想显式指定逻辑,可以取消下面这行的注释:
// .ForMember(dest => dest.Body, opt => opt.MapFrom(src => src.Body))
.ForMember(dest => dest.Ext, opt => opt.MapFrom(src => src.Ext))
.ForMember(dest => dest.Quote, opt => opt.MapFrom(src =>
src.Quote.MessageId == Guid.Empty ? null : src.Quote));
// 3. 配置 Message -> MessageResponse
CreateMap<Domain.Entities.Message, MessageResponse>()
// 映射审计字段中的创建时间
.ForMember(dest => dest.CreationTime, opt => opt.MapFrom(src => src.CreationTime))
// 嵌套映射 Content
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content));
}
}
}
@@ -0,0 +1,112 @@
using AutoMapper;
using IM.Commons;
using MessageService.Domain.Enums;
using MessageService.Domain.IReposities;
using MessageService.Domain.KeyObjects;
using MessageService.Domain.Tools;
using MessageService.WebApi.Application.Dtos;
using MessageService.WebApi.Application.IntegrationServices;
namespace MessageService.WebApi.Application.Message
{
public class MessageService
{
private readonly IMessageReposity reposity;
private readonly IMapper mapper;
private readonly IGroupMemberIntegrationService memberService;
private readonly IContactIntegrationService contactService;
private readonly SquenceService squenceService;
public MessageService(IMessageReposity reposity, IMapper mapper,
IGroupMemberIntegrationService memberService,
IContactIntegrationService contactService,
SquenceService squenceService
)
{
this.reposity = reposity;
this.mapper = mapper;
this.memberService = memberService;
this.contactService = contactService;
this.squenceService = squenceService;
}
public async Task<Result<MessageResponse>> SendMsgAsync(SendMsgCommand command)
{
if (command.ChatType == Domain.Enums.ChatType.PRIVATE)
{
bool passed = await contactService.CheckContactAsync(command.SenderId, command.TargetId);
if (!passed)
return Result.Fail<MessageResponse>(ResultCode.FRIEND_RELATION_NOT_FOUND);
}
else
{
bool passed = await memberService.CheckGroupMemberAsync(command.SenderId, command.TargetId);
if (!passed)
return Result.Fail<MessageResponse>(ResultCode.NO_GROUP_PERMISSION);
}
var ctx = new MessageCreateContext(command.ChatType, command.ClientMsgId, command.SenderId, command.TargetId);
var streamKey = command.ChatType == ChatType.GROUP ? StreamKeyBuilder.Group(ctx.TargetId) : StreamKeyBuilder.Private(ctx.SenderId, ctx.TargetId);
long sequenceId = await squenceService.GetNextSquenceIdAsync(streamKey);
Domain.Entities.Message message = command.MsgType switch
{
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.Video => Domain.Entities.Message.BuildVideo(ctx, command.Url!,
command.Width ?? 0, command.Height ?? 0, command.Thumb!, sequenceId),
MessageType.Voice => Domain.Entities.Message.BuildVoice(ctx, command.Url!, command.Duration ?? 0, sequenceId),
_ => null
};
if (message == null)
{
return Result.Fail<MessageResponse>(ResultCode.UNSUPPORTED_MESSAGE_TYPE);
}
// 2. 处理引用逻辑(如果传了 QuoteMessageId
QuoteInfo? quote = null;
if (command.QuoteMessageId.HasValue)
{
var originMsg = await reposity.FindByIdAsync(command.QuoteMessageId.Value);
if (originMsg != null)
{
quote = new QuoteInfo
{
MessageId = originMsg.Id,
SenderId = originMsg.SenderId,
SenderName = "未知昵称", // 这里建议从缓存或DB获取发送者昵称
MessageType = originMsg.MsgType,
Preview = originMsg.Content.Fallback
};
}
}
message.WithQuote(quote);
reposity.Create(message);
return Result.Success(mapper.Map<MessageResponse>(message));
}
public async Task<Result<object>> WithDrawMsgAsync(Guid msgId, Guid senderId)
{
var msg = await reposity.FindByIdAsync(msgId);
if (msg == null || msg.SenderId != senderId)
{
return Result.Fail<object>(ResultCode.MESSAGE_NOT_FOUND);
}
msg.Withdraw();
return Result.Success();
}
}
}
@@ -0,0 +1,43 @@
using MessageService.Domain.Enums;
namespace MessageService.WebApi.Application.Message
{
public record SendMsgCommand
{
// 核心区别:Command 必须包含 SenderId,这是从后端 Token 解析出来的
public Guid SenderId { get; init; }
public Guid TargetId { get; init; }
public ChatType ChatType { get; init; }
public MessageType MsgType { get; init; }
public Guid ClientMsgId { get; init; }
public Guid? QuoteMessageId { get; init; }
public Dictionary<string, string>? Ext { get; init; }
// 拍扁后的参数,方便 Service 直接调用工厂方法
public string? Text { get; init; }
public string? Url { get; init; }
public int? Width { get; init; }
public int? Height { get; init; }
public string? Thumb { get; init; }
public int? Duration { 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)
{
SenderId = senderId;
TargetId = targetId;
ChatType = chatType;
MsgType = msgType;
ClientMsgId = clientMsgId;
QuoteMessageId = quoteMessageId;
Ext = ext;
Text = text;
Url = url;
Width = width;
Height = height;
Thumb = thumb;
Duration = duration;
}
}
}
@@ -0,0 +1,47 @@
using IM.Commons;
using MessageService.Infrastructure;
using Microsoft.EntityFrameworkCore;
using RedLockNet;
using StackExchange.Redis;
namespace MessageService.WebApi.Application
{
public class SquenceService
{
private readonly IDatabase _database;
private readonly IDistributedLockFactory _lockFactory;
private readonly MessageDbContext messageDb;
public SquenceService(IConnectionMultiplexer multiplexer,
IDistributedLockFactory distributedLockFactory,
MessageDbContext messageDb
)
{
_database = multiplexer.GetDatabase();
_lockFactory = distributedLockFactory;
this.messageDb = messageDb;
}
public async Task<long> GetNextSquenceIdAsync(string streamKey)
{
string key = RedisHelper.GetSequenceIdKey(streamKey);
string lockKey = RedisHelper.GetSequenceIdLockKey(streamKey);
var exists = await _database.KeyExistsAsync(key);
if (!exists)
{
using (var _lock = await _lockFactory.CreateLockAsync(lockKey, TimeSpan.FromSeconds(5)))
{
if (_lock.IsAcquired)
{
if (!await _database.KeyExistsAsync(key))
{
var max = await messageDb.Messages
.Where(x => x.StreamKey == streamKey)
.MaxAsync(m => (long?)m.SequenceId) ?? 0;
await _database.StringSetAsync(key, max, TimeSpan.FromDays(7));
}
}
}
}
return await _database.StringIncrementAsync(key);
}
}
}
@@ -0,0 +1,34 @@
using MessageService.WebApi.Application.Conversation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace MessageService.WebApi.Controllers.Conversation
{
[Route("api/[controller]/[action]")]
[Authorize]
[ApiController]
public class ConversationController : ControllerBase
{
private readonly ConversationService service;
public ConversationController(ConversationService service)
{
this.service = service;
}
[HttpGet]
public async Task<IActionResult> List()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
return Ok(await service.GetByOwnerIdAsync(Guid.Parse(userId)));
}
[HttpGet]
public async Task<IActionResult> Get([FromRoute] Guid id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
return Ok(await service.GetByIdAsync(id, Guid.Parse(userId)));
}
}
}
@@ -0,0 +1,38 @@
using IM.ASPNETCore;
using MessageService.Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace MessageService.WebApi.Controllers.Message
{
[Route("api/[controller]/[action]")]
[Authorize]
[ApiController]
public class MessageController : ControllerBase
{
private readonly Application.Message.MessageService service;
public MessageController(Application.Message.MessageService service)
{
this.service = service;
}
[HttpPost]
[UnitOfWork(typeof(MessageDbContext))]
public async Task<IActionResult> Send(MessageSendRequest request)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var command = request.ToCommand(Guid.Parse(userId));
return Ok(await service.SendMsgAsync(command));
}
[HttpPost]
[UnitOfWork(typeof(MessageDbContext))]
public async Task<IActionResult> WithDraw([FromRoute] Guid msgId)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
return Ok(await service.WithDrawMsgAsync(msgId, Guid.Parse(userId)));
}
}
}
@@ -0,0 +1,59 @@
using FluentValidation;
using MessageService.Domain.Enums;
using MessageService.WebApi.Application.Message;
namespace MessageService.WebApi.Controllers.Message
{
public class MessageSendRequest
{
// 基础元数据
public Guid ClientMsgId { get; init; }
public Guid TargetId { get; init; }
public ChatType ChatType { get; init; }
public MessageType MsgType { get; init; }
// 业务属性
public Guid? QuoteMessageId { get; init; }
public Dictionary<string, string>? Ext { get; init; }
// 载荷数据(根据 MsgType 选择性填充)
public string? Text { get; init; }
public string? Url { get; init; }
public int? Width { get; init; }
public int? Height { get; init; }
public string? Thumb { get; init; }
public int? Duration { get; init; }
public SendMsgCommand ToCommand(Guid senderId)
{
return new SendMsgCommand(
senderId,
TargetId,
ChatType,
MsgType,
ClientMsgId,
QuoteMessageId,
Ext,
Text,
Url,
Width,
Height,
Thumb,
Duration
);
}
}
public class MessageSendRequestValidator : AbstractValidator<MessageSendRequest>
{
public MessageSendRequestValidator()
{
RuleFor(r => r.ClientMsgId)
.NotNull()
.NotEmpty();
RuleFor(r => r.TargetId)
.NotEmpty()
.NotNull();
}
}
}
@@ -0,0 +1,18 @@
using IM.InitCommon;
using MessageService.Infrastructure;
using Microsoft.EntityFrameworkCore.Design;
namespace MessageService.WebApi
{
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MessageDbContext>
{
public MessageDbContext CreateDbContext(string[] args)
{
// 1. 复用你写好的配置工厂,提取连接字符串
var optionsBuilder = DbContextOptionsBuilderFactory.Create<MessageDbContext>();
// 2. 🌟 关键补刀:把假的 Mediator 传进去,满足构造函数的要求!
return new MessageDbContext(optionsBuilder.Options, null);
}
}
}
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="MassTransit" Version="9.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RedLock.net" Version="2.3.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IM.ASPNETCore\IM.ASPNETCore.csproj" />
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
<ProjectReference Include="..\IM.Protocols\IM.Protocols.csproj" />
<ProjectReference Include="..\MessageService.Domain\MessageService.Domain.csproj" />
<ProjectReference Include="..\MessageService.Infrastructure\MessageService.Infrastructure.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
@MessageService.WebApi_HostAddress = http://localhost:5067
GET {{MessageService.WebApi_HostAddress}}/weatherforecast/
Accept: application/json
###
+25
View File
@@ -0,0 +1,25 @@
using IM.Commons;
using IM.Protocols.Grpc.Contact;
using IM.Protocols.Grpc.User;
using MessageService.WebApi.Application;
using MessageService.WebApi.Application.IntegrationServices;
using Microsoft.Extensions.Options;
namespace MessageService.WebApi
{
public class ModuleInit : IModuleInitializer
{
public void Initialize(IServiceCollection services)
{
services.AddScoped<Application.Message.MessageService>();
services.AddScoped<SquenceService>();
services.AddScoped<IContactIntegrationService, ContactIntegrationService>();
services.AddScoped<IGroupMemberIntegrationService, GroupMemberIntegrationService>();
services.AddGrpcClient<ContactInternal.ContactInternalClient>((sp, o) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<GrpcOptions>>();
o.Address = new Uri(options.CurrentValue.ContactServiceUrl);
});
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using IM.InitCommon;
namespace MessageService.WebApi
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.ConfigureDbConfiguration();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.ConfigExtraServices();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAppDefault();
app.MapControllers();
app.Run();
}
}
}
@@ -0,0 +1,41 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5067"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7143;http://localhost:5067"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
},
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:25172",
"sslPort": 44390
}
},
"$schema": "http://json.schemastore.org/launchsettings.json"
}
@@ -0,0 +1,28 @@
using Grpc.Core;
using IM.Protocols.Grpc.Conversation;
using MessageService.WebApi.Application.Conversation;
namespace MessageService.WebApi.Services
{
public class ConversationIntegrationService:ConversationInternal.ConversationInternalBase
{
private readonly ConversationService service;
public ConversationIntegrationService(ConversationService service)
{
this.service = service;
}
public override async Task<UserStreamKeysResponse> GetUserStreamKeys(GetUserStreamKeysRequest request, ServerCallContext context)
{
var res = await service.GetStreamkeysAsync(Guid.Parse(request.UserId));
if (!res.Succeeded)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, res.Message));
}
var response = new UserStreamKeysResponse();
response.StreamKeys.AddRange(res.Data);
return response;
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}