添加项目文件。
This commit is contained in:
parent
c60f5fe117
commit
720ef957d4
20
ConnectorService/ConnectorService.csproj
Normal file
20
ConnectorService/ConnectorService.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\IM.ASPNETCore\IM.ASPNETCore.csproj" />
|
||||
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
|
||||
<ProjectReference Include="..\IM.Protocols\IM.Protocols.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
ConnectorService/ConnectorService.http
Normal file
6
ConnectorService/ConnectorService.http
Normal file
@ -0,0 +1,6 @@
|
||||
@ConnectorService_HostAddress = http://localhost:5100
|
||||
|
||||
GET {{ConnectorService_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
24
ConnectorService/Consumers/MessageConsumer.cs
Normal file
24
ConnectorService/Consumers/MessageConsumer.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using ConnectorService.Dtos;
|
||||
using ConnectorService.Hubs;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ConnectorService.Consumers
|
||||
{
|
||||
public class MessageConsumer : IConsumer<MsgCreatedEvent>
|
||||
{
|
||||
private readonly IHubContext<ChatHub> hub;
|
||||
|
||||
public MessageConsumer(IHubContext<ChatHub> hub)
|
||||
{
|
||||
this.hub = hub;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<MsgCreatedEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
await hub.Clients.Group(@event.StreamKey).SendAsync("ReceiveNewMessage", @event.ToHubResponse());
|
||||
}
|
||||
}
|
||||
}
|
||||
30
ConnectorService/Dockerfile
Normal file
30
ConnectorService/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY IM_API_NEW.sln ./
|
||||
|
||||
COPY ConnectorService/ConnectorService.csproj ConnectorService/
|
||||
COPY IM.Commons/IM.Commons.csproj IM.Commons/
|
||||
COPY IM.InitCommon/IM.InitCommon.csproj IM.InitCommon/
|
||||
COPY IM.Protocols/IM.Protocols.csproj IM.Protocols/
|
||||
|
||||
RUN dotnet restore ConnectorService/ConnectorService.csproj
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN dotnet publish ConnectorService/ConnectorService.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "ConnectorService.dll"]
|
||||
81
ConnectorService/Dtos/MessageHubResponse.cs
Normal file
81
ConnectorService/Dtos/MessageHubResponse.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using IM.Commons.IntegrationEvents;
|
||||
|
||||
namespace ConnectorService.Dtos
|
||||
{
|
||||
public record MessageHubResponse
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户端去重/回执使用的本地 ID
|
||||
/// </summary>
|
||||
public Guid ClientId { get; init; }
|
||||
|
||||
public string ChatType { get; init; } = string.Empty;
|
||||
public string MsgType { get; init; } = string.Empty;
|
||||
public Guid SenderId { get; init; }
|
||||
public Guid TargetId { get; init; }
|
||||
public string State { get; init; } = string.Empty;
|
||||
public string StreamKey { get; init; } = string.Empty;
|
||||
public long SequenceId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务器推送到达时间 (毫秒级时间戳,强烈建议加上)
|
||||
/// </summary>
|
||||
public long PushTimestamp { get; init; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
public HubMsgContentDto Content { get; init; } = null!;
|
||||
}
|
||||
|
||||
// 嵌套的内容对象,允许 Ext 和 Quote 为 null 以缩减 JSON 体积
|
||||
public record HubMsgContentDto(
|
||||
string Fallback,
|
||||
object Body,
|
||||
Dictionary<string, string>? Ext,
|
||||
HubQuoteInfoDto? Quote
|
||||
);
|
||||
|
||||
public record HubQuoteInfoDto(
|
||||
Guid MessageId,
|
||||
Guid SenderId,
|
||||
string SenderName,
|
||||
string MessageType,
|
||||
string Preview
|
||||
);
|
||||
public static class MessageEventMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 将内部集成事件转换为对外推送的 DTO
|
||||
/// </summary>
|
||||
public static MessageHubResponse ToHubResponse(this MsgCreatedEvent @event)
|
||||
{
|
||||
if (@event == null) throw new ArgumentNullException(nameof(@event));
|
||||
|
||||
return new MessageHubResponse
|
||||
{
|
||||
Id = @event.Id,
|
||||
ClientId = @event.ClientId,
|
||||
ChatType = @event.ChatType,
|
||||
MsgType = @event.MsgType,
|
||||
SenderId = @event.SenderId,
|
||||
TargetId = @event.TargetId,
|
||||
State = @event.State,
|
||||
StreamKey = @event.StreamKey,
|
||||
SequenceId = @event.SequenceId,
|
||||
// 嵌套映射
|
||||
Content = @event.Content != null ? new HubMsgContentDto(
|
||||
@event.Content.Fallback,
|
||||
@event.Content.Body,
|
||||
@event.Content.Ext,
|
||||
@event.Content.Quote != null ? new HubQuoteInfoDto(
|
||||
@event.Content.Quote.MessageId,
|
||||
@event.Content.Quote.SenderId,
|
||||
@event.Content.Quote.SenderName,
|
||||
@event.Content.Quote.MessageType,
|
||||
@event.Content.Quote.Preview
|
||||
) : null
|
||||
) : null!
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
53
ConnectorService/Hubs/ChatHub.cs
Normal file
53
ConnectorService/Hubs/ChatHub.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using ConnectorService.Services;
|
||||
using IM.Commons;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using StackExchange.Redis;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace ConnectorService.Hubs
|
||||
{
|
||||
public class ChatHub : Hub
|
||||
{
|
||||
private readonly IConversationIntergrationService conService;
|
||||
private readonly StackExchange.Redis.IDatabase redis;
|
||||
|
||||
public ChatHub(IConversationIntergrationService conService, IConnectionMultiplexer multiplexer)
|
||||
{
|
||||
this.conService = conService;
|
||||
this.redis = multiplexer.GetDatabase();
|
||||
}
|
||||
|
||||
public async override Task OnConnectedAsync()
|
||||
{
|
||||
if (!Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
Context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
var res = await conService.GetUserStreamKeysAsync(Guid.Parse(userId));
|
||||
foreach (var streamkey in res)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, streamkey);
|
||||
}
|
||||
|
||||
await redis.SetAddAsync(RedisHelper.GetConnectionIdKey(userId), Context.ConnectionId);
|
||||
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public async override Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
if (Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
var userId = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
await redis.SetRemoveAsync(RedisHelper.GetConnectionIdKey(userId), Context.ConnectionId);
|
||||
}
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ConnectorService/ModuleInit.cs
Normal file
19
ConnectorService/ModuleInit.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using IM.Commons;
|
||||
using IM.Protocols.Grpc.Conversation;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ConnectorService
|
||||
{
|
||||
public class ModuleInit : IModuleInitializer
|
||||
{
|
||||
public void Initialize(IServiceCollection services)
|
||||
{
|
||||
services.AddRedisCache();
|
||||
services.AddGrpcClient<ConversationInternal.ConversationInternalClient>((sp ,o) =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptionsMonitor<GrpcOptions>>();
|
||||
o.Address = new Uri(options.CurrentValue.MessageServiceUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
42
ConnectorService/Program.cs
Normal file
42
ConnectorService/Program.cs
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
using ConnectorService.Hubs;
|
||||
using IM.InitCommon;
|
||||
|
||||
namespace ConnectorService
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.ConfigureDbConfiguration();
|
||||
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
// 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.MapHub<ChatHub>("/chat");
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
41
ConnectorService/Properties/launchSettings.json
Normal file
41
ConnectorService/Properties/launchSettings.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:46392",
|
||||
"sslPort": 44313
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5100",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7115;http://localhost:5100",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
ConnectorService/Services/ConversationIntegrationService.cs
Normal file
30
ConnectorService/Services/ConversationIntegrationService.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using IM.Commons;
|
||||
using IM.Protocols.Grpc.Conversation;
|
||||
|
||||
namespace ConnectorService.Services
|
||||
{
|
||||
public class ConversationIntegrationService : IConversationIntergrationService
|
||||
{
|
||||
private readonly ConversationInternal.ConversationInternalClient client;
|
||||
public async Task<List<string>> GetUserStreamKeysAsync(Guid userId)
|
||||
{
|
||||
var req = new GetUserStreamKeysRequest()
|
||||
{
|
||||
UserId = userId.ToString()
|
||||
};
|
||||
var res = await client.GetUserStreamKeysAsync(req);
|
||||
if(res == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var list = new List<string>();
|
||||
foreach(var item in res.StreamKeys)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using IM.Commons;
|
||||
|
||||
namespace ConnectorService.Services
|
||||
{
|
||||
public interface IConversationIntergrationService
|
||||
{
|
||||
Task<List<string>> GetUserStreamKeysAsync(Guid userId);
|
||||
}
|
||||
}
|
||||
8
ConnectorService/appsettings.Development.json
Normal file
8
ConnectorService/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
ConnectorService/appsettings.json
Normal file
9
ConnectorService/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
14
ContactService.Domain/ContactService.Domain.csproj
Normal file
14
ContactService.Domain/ContactService.Domain.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\DomainCommons\IM.DomainCommons.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
54
ContactService.Domain/Entities/Friend.cs
Normal file
54
ContactService.Domain/Entities/Friend.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using ContactService.Domain.Events;
|
||||
using ContactService.Domain.ValueObjects;
|
||||
using IM.DomainCommons;
|
||||
|
||||
namespace ContactService.Domain.Entities
|
||||
{
|
||||
public class Friend : AggregateRootEntity
|
||||
{
|
||||
public UserProfile Owner { get; private set; }
|
||||
public UserProfile Target { get; private set; }
|
||||
/// <summary>
|
||||
/// 好友备注名
|
||||
/// </summary>
|
||||
public string? RemarkName { get; private set; }
|
||||
public FriendStatus Status { get; private set; }
|
||||
|
||||
private Friend() { }
|
||||
|
||||
public Friend(UserProfile owner, UserProfile target, string? remarkName)
|
||||
{
|
||||
Owner = owner;
|
||||
Target = target;
|
||||
RemarkName = remarkName;
|
||||
Status = FriendStatus.Added;
|
||||
|
||||
AddDomainEvent(new FriendAddedDomainEvent(this));
|
||||
}
|
||||
|
||||
public void setRemarkName(string? remarkName)
|
||||
{
|
||||
if (remarkName.Length > 20)
|
||||
{
|
||||
throw new DomainException("备注名过长");
|
||||
}
|
||||
RemarkName = remarkName ?? RemarkName;
|
||||
}
|
||||
|
||||
public void Block()
|
||||
{
|
||||
Status = FriendStatus.Blocked;
|
||||
AddDomainEvent(new FriendBlockDomainEvent(this));
|
||||
}
|
||||
|
||||
public void UpdateUserInfo(UserProfile profile)
|
||||
{
|
||||
if (profile.Id != Target.Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Target = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
ContactService.Domain/Entities/FriendRequest.cs
Normal file
73
ContactService.Domain/Entities/FriendRequest.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using ContactService.Domain.Events;
|
||||
using IM.DomainCommons;
|
||||
|
||||
namespace ContactService.Domain.Entities
|
||||
{
|
||||
public class FriendRequest : AggregateRootEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 申请人
|
||||
/// </summary>
|
||||
public Guid OwnerId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 被申请人
|
||||
/// </summary>
|
||||
public Guid TargetId { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 申请附言
|
||||
/// </summary>
|
||||
public string Description { get; private set; } = "申请添加好友";
|
||||
|
||||
/// <summary>
|
||||
/// 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑)
|
||||
/// </summary>
|
||||
public FriendRequestStatus State { get; private set; } = FriendRequestStatus.Pending;
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? RemarkName { get; private set; }
|
||||
|
||||
private FriendRequest() { }
|
||||
|
||||
public FriendRequest(Guid ownerId, Guid targetId, string? description, string? remarkName)
|
||||
{
|
||||
OwnerId = ownerId;
|
||||
TargetId = targetId;
|
||||
Description = description ?? Description;
|
||||
RemarkName = remarkName;
|
||||
AddDomainEvent(new FriendRequestCreatedDomainEvent(this));
|
||||
}
|
||||
public void Accept(string remarkName)
|
||||
{
|
||||
if (State != FriendRequestStatus.Pending)
|
||||
{
|
||||
throw new DomainException("只能处理待处理的好友请求");
|
||||
}
|
||||
State = FriendRequestStatus.Passed;
|
||||
AddDomainEvent(new FriendRequestStateUpdateDomainEvent(this,remarkName));
|
||||
}
|
||||
|
||||
public void Reject()
|
||||
{
|
||||
if (State != FriendRequestStatus.Pending)
|
||||
{
|
||||
throw new DomainException("只能处理待处理的好友请求");
|
||||
}
|
||||
State = FriendRequestStatus.Declined;
|
||||
AddDomainEvent(new FriendRequestStateUpdateDomainEvent(this));
|
||||
}
|
||||
public void Block()
|
||||
{
|
||||
if (State != FriendRequestStatus.Pending)
|
||||
{
|
||||
throw new DomainException("只能处理待处理的好友请求");
|
||||
}
|
||||
State = FriendRequestStatus.Blocked;
|
||||
AddDomainEvent(new FriendRequestStateUpdateDomainEvent(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
7
ContactService.Domain/Events/FriendAddedDomainEvent.cs
Normal file
7
ContactService.Domain/Events/FriendAddedDomainEvent.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.Domain.Events
|
||||
{
|
||||
public record FriendAddedDomainEvent(Friend Friend) : INotification;
|
||||
}
|
||||
7
ContactService.Domain/Events/FriendBlockDomainEvent.cs
Normal file
7
ContactService.Domain/Events/FriendBlockDomainEvent.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.Domain.Events
|
||||
{
|
||||
public record FriendBlockDomainEvent(Friend Friend) : INotification;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.Domain.Events
|
||||
{
|
||||
public record FriendRequestCreatedDomainEvent(FriendRequest Request) : INotification;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.Domain.Events
|
||||
{
|
||||
public record FriendRequestStateUpdateDomainEvent(FriendRequest Request,string? AcceptRemarkName = default) : INotification;
|
||||
}
|
||||
28
ContactService.Domain/FriendDomainService.cs
Normal file
28
ContactService.Domain/FriendDomainService.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using ContactService.Domain.ValueObjects;
|
||||
using IM.Commons;
|
||||
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public class FriendDomainService
|
||||
{
|
||||
private readonly IFriendReposity reposity;
|
||||
|
||||
public FriendDomainService(IFriendReposity reposity)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
}
|
||||
|
||||
public async Task<Result<Friend?>> CreateAsync(UserProfile owner, UserProfile target, string? remarkName)
|
||||
{
|
||||
var isExist = await reposity.CheckOwnerIdAndTargetIdAsync(owner.Id, target.Id);
|
||||
if (isExist)
|
||||
{
|
||||
return Result<Friend?>.Fail(ResultCode.ALREADY_FRIENDS);
|
||||
}
|
||||
var friend = new Friend(owner, target, remarkName);
|
||||
var res = await reposity.CreateAsync(friend);
|
||||
return Result<Friend?>.Success(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ContactService.Domain/FriendRequestDomainService.cs
Normal file
22
ContactService.Domain/FriendRequestDomainService.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using IM.Commons;
|
||||
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public class FriendRequestDomainService
|
||||
{
|
||||
private readonly IFriendRequestReposity reposity;
|
||||
|
||||
public FriendRequestDomainService(IFriendRequestReposity reposity)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
}
|
||||
|
||||
public async Task<Result<FriendRequest?>> CreateAsync(Guid ownerId, Guid targetId, string? description, string? remarkName)
|
||||
{
|
||||
var friendRequest = new FriendRequest(ownerId, targetId, description, remarkName);
|
||||
await reposity.CreateAsync(friendRequest);
|
||||
return Result<FriendRequest?>.Success(friendRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ContactService.Domain/FriendRequestStatus.cs
Normal file
22
ContactService.Domain/FriendRequestStatus.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public enum FriendRequestStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待处理
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
/// <summary>
|
||||
/// 已通过
|
||||
/// </summary>
|
||||
Passed = 2,
|
||||
/// <summary>
|
||||
/// 已拒绝
|
||||
/// </summary>
|
||||
Declined = 1,
|
||||
/// <summary>
|
||||
/// 拉黑
|
||||
/// </summary>
|
||||
Blocked = 3
|
||||
}
|
||||
}
|
||||
22
ContactService.Domain/FriendStatus.cs
Normal file
22
ContactService.Domain/FriendStatus.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public enum FriendStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待处理
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
/// <summary>
|
||||
/// 已添加
|
||||
/// </summary>
|
||||
Added = 1,
|
||||
/// <summary>
|
||||
/// 已拒绝
|
||||
/// </summary>
|
||||
Declined = 2,
|
||||
/// <summary>
|
||||
/// 已拉黑
|
||||
/// </summary>
|
||||
Blocked = 3
|
||||
}
|
||||
}
|
||||
16
ContactService.Domain/IFriendReposity.cs
Normal file
16
ContactService.Domain/IFriendReposity.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using ContactService.Domain.Entities;
|
||||
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public interface IFriendReposity
|
||||
{
|
||||
Task<Friend?> FindByIdAsync(Guid id);
|
||||
Task<Friend?> FindByOwnerAndTargetAsync(Guid ownerId, Guid targetId);
|
||||
Task<IEnumerable<Friend>> FindByTargetAsync(Guid targetId);
|
||||
Task<IEnumerable<Friend>> FindByOwnerAsync(Guid ownerId);
|
||||
|
||||
Task<Friend> CreateAsync(Friend friend);
|
||||
|
||||
Task<bool> CheckOwnerIdAndTargetIdAsync(Guid ownerId, Guid targetId);
|
||||
}
|
||||
}
|
||||
13
ContactService.Domain/IFriendRequestReposity.cs
Normal file
13
ContactService.Domain/IFriendRequestReposity.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using ContactService.Domain.Entities;
|
||||
|
||||
namespace ContactService.Domain
|
||||
{
|
||||
public interface IFriendRequestReposity
|
||||
{
|
||||
Task<bool> CreateAsync(FriendRequest friendRequest);
|
||||
Task<FriendRequest?> FindByIdAsync(Guid id);
|
||||
Task<IEnumerable<FriendRequest>> FindByOwnerIdAsync(Guid ownerId);
|
||||
Task<IEnumerable<FriendRequest>> FindByTargetIdAsync(Guid targetId);
|
||||
|
||||
}
|
||||
}
|
||||
17
ContactService.Domain/ValueObjects/UserProfile.cs
Normal file
17
ContactService.Domain/ValueObjects/UserProfile.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace ContactService.Domain.ValueObjects
|
||||
{
|
||||
public class UserProfile
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public string NickName { get; private set; }
|
||||
public string? Avatar { get; private set; }
|
||||
public UserProfile() { }
|
||||
|
||||
public UserProfile(Guid id, string nickName, string? avatar)
|
||||
{
|
||||
Id = id;
|
||||
NickName = nickName;
|
||||
Avatar = avatar;
|
||||
}
|
||||
};
|
||||
}
|
||||
35
ContactService.Infrastructure/Configs/FriendConfig.cs
Normal file
35
ContactService.Infrastructure/Configs/FriendConfig.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ContactService.Infrastructure.Configs
|
||||
{
|
||||
public class FriendConfig : IEntityTypeConfiguration<Friend>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Friend> builder)
|
||||
{
|
||||
builder.ToTable("friends");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.OwnsOne(x => x.Owner, owner =>
|
||||
{
|
||||
owner.WithOwner(); // 🔥 关键:明确归属
|
||||
|
||||
owner.Property(p => p.Id).HasColumnName("OwnerId").IsRequired();
|
||||
owner.Property(p => p.NickName).HasColumnName("OwnerNickName");
|
||||
owner.Property(p => p.Avatar).HasColumnName("OwnerAvatarUrl");
|
||||
});
|
||||
|
||||
builder.OwnsOne(x => x.Target, target =>
|
||||
{
|
||||
target.WithOwner(); // 🔥 关键
|
||||
|
||||
target.Property(p => p.Id).HasColumnName("TargetId").IsRequired();
|
||||
target.Property(p => p.NickName).HasColumnName("TargetNickName");
|
||||
target.Property(p => p.Avatar).HasColumnName("TargetAvatarUrl");
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
20
ContactService.Infrastructure/Configs/FriendRequestConfig.cs
Normal file
20
ContactService.Infrastructure/Configs/FriendRequestConfig.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ContactService.Infrastructure.Configs
|
||||
{
|
||||
public class FriendRequestConfig : IEntityTypeConfiguration<FriendRequest>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<FriendRequest> builder)
|
||||
{
|
||||
builder.ToTable("friend_requests");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.HasIndex(x => new { x.OwnerId, x.TargetId });
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
24
ContactService.Infrastructure/ContactDbContext.cs
Normal file
24
ContactService.Infrastructure/ContactDbContext.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using ContactService.Domain.Entities;
|
||||
using IM.Infrastructure.Efcore;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ContactService.Infrastructure
|
||||
{
|
||||
public class ContactDbContext : BaseDbContext
|
||||
{
|
||||
public DbSet<Friend> Friends { get; private set; }
|
||||
public DbSet<FriendRequest> FriendRequests { get; private set; }
|
||||
public ContactDbContext(DbContextOptions options, IMediator mediator) : base(options, mediator)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
|
||||
|
||||
modelBuilder.EnableSoftDeletionGlobalFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\ContactService.Domain\ContactService.Domain.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\IM.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
46
ContactService.Infrastructure/FriendReposity.cs
Normal file
46
ContactService.Infrastructure/FriendReposity.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using ContactService.Domain;
|
||||
using ContactService.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ContactService.Infrastructure
|
||||
{
|
||||
public class FriendReposity : IFriendReposity
|
||||
{
|
||||
private readonly ContactDbContext db;
|
||||
|
||||
public FriendReposity(ContactDbContext db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckOwnerIdAndTargetIdAsync(Guid ownerId, Guid targetId)
|
||||
{
|
||||
var exist = await db.Friends.AnyAsync(x => x.Owner.Id == ownerId && x.Target.Id == targetId);
|
||||
return exist;
|
||||
}
|
||||
|
||||
public async Task<Friend> CreateAsync(Friend friend)
|
||||
{
|
||||
db.Add(friend);
|
||||
return friend;
|
||||
}
|
||||
|
||||
public Task<Friend?> FindByIdAsync(Guid id)
|
||||
{
|
||||
return db.Friends.FirstOrDefaultAsync(x => x.Id == id);
|
||||
}
|
||||
|
||||
public Task<Friend?> FindByOwnerAndTargetAsync(Guid ownerId, Guid targetId)
|
||||
{
|
||||
return db.Friends.FirstOrDefaultAsync(x => x.Owner.Id == ownerId && x.Target.Id == targetId);
|
||||
}
|
||||
public async Task<IEnumerable<Friend>> FindByTargetAsync(Guid targetId)
|
||||
{
|
||||
return await db.Friends.Where(x => x.Target.Id == targetId).ToListAsync();
|
||||
}
|
||||
public async Task<IEnumerable<Friend>> FindByOwnerAsync(Guid ownerId)
|
||||
{
|
||||
return await db.Friends.Where(x => x.Owner.Id == ownerId).ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
ContactService.Infrastructure/FriendRequestReposity.cs
Normal file
37
ContactService.Infrastructure/FriendRequestReposity.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using ContactService.Domain;
|
||||
using ContactService.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ContactService.Infrastructure
|
||||
{
|
||||
public class FriendRequestReposity : IFriendRequestReposity
|
||||
{
|
||||
private readonly ContactDbContext db;
|
||||
|
||||
public FriendRequestReposity(ContactDbContext db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(FriendRequest friendRequest)
|
||||
{
|
||||
db.Add(friendRequest);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<FriendRequest?> FindByIdAsync(Guid id)
|
||||
{
|
||||
return db.FriendRequests.FirstOrDefaultAsync(x => x.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FriendRequest>> FindByOwnerIdAsync(Guid ownerId)
|
||||
{
|
||||
return await db.FriendRequests.Where(x => x.OwnerId == ownerId).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FriendRequest>> FindByTargetIdAsync(Guid targetId)
|
||||
{
|
||||
return await db.FriendRequests.Where(x => x.TargetId == targetId).ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
161
ContactService.Infrastructure/Migrations/20260413114340_InitDb.Designer.cs
generated
Normal file
161
ContactService.Infrastructure/Migrations/20260413114340_InitDb.Designer.cs
generated
Normal file
@ -0,0 +1,161 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ContactService.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ContactService.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContactDbContext))]
|
||||
[Migration("20260413114340_InitDb")]
|
||||
partial class InitDb
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.Friend", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTimeOffset?>("Deletion")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTimeOffset?>("ModificationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("RemarkName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("friends", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.FriendRequest", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTimeOffset?>("Deletion")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTimeOffset?>("ModificationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("OwnerId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("RemarkName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TargetId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId", "TargetId");
|
||||
|
||||
b.ToTable("friend_requests", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.Friend", b =>
|
||||
{
|
||||
b.OwnsOne("ContactService.Domain.ValueObjects.UserProfile", "Owner", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FriendId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b1.Property<string>("Avatar")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("OwnerAvatarUrl");
|
||||
|
||||
b1.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("OwnerId");
|
||||
|
||||
b1.Property<string>("NickName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("OwnerNickName");
|
||||
|
||||
b1.HasKey("FriendId");
|
||||
|
||||
b1.ToTable("friends");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FriendId");
|
||||
});
|
||||
|
||||
b.OwnsOne("ContactService.Domain.ValueObjects.UserProfile", "Target", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FriendId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b1.Property<string>("Avatar")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("TargetAvatarUrl");
|
||||
|
||||
b1.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("TargetId");
|
||||
|
||||
b1.Property<string>("NickName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("TargetNickName");
|
||||
|
||||
b1.HasKey("FriendId");
|
||||
|
||||
b1.ToTable("friends");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FriendId");
|
||||
});
|
||||
|
||||
b.Navigation("Owner")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Target")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ContactService.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitDb : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("MySql:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "friend_requests",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OwnerId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
TargetId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
State = table.Column<int>(type: "int", nullable: false),
|
||||
RemarkName = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
IsDeleted = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
|
||||
Deletion = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
|
||||
ModificationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_friend_requests", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "friends",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OwnerId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OwnerNickName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
OwnerAvatarUrl = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
TargetId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
TargetNickName = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
TargetAvatarUrl = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
RemarkName = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:Charset", "utf8mb4"),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
IsDeleted = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
|
||||
Deletion = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
|
||||
ModificationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_friends", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_friend_requests_OwnerId_TargetId",
|
||||
table: "friend_requests",
|
||||
columns: new[] { "OwnerId", "TargetId" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "friend_requests");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "friends");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ContactService.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ContactService.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContactDbContext))]
|
||||
partial class ContactDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.Friend", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTimeOffset?>("Deletion")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTimeOffset?>("ModificationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("RemarkName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("friends", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.FriendRequest", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTimeOffset?>("Deletion")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTimeOffset?>("ModificationTime")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("OwnerId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("RemarkName")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TargetId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId", "TargetId");
|
||||
|
||||
b.ToTable("friend_requests", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ContactService.Domain.Entities.Friend", b =>
|
||||
{
|
||||
b.OwnsOne("ContactService.Domain.ValueObjects.UserProfile", "Owner", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FriendId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b1.Property<string>("Avatar")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("OwnerAvatarUrl");
|
||||
|
||||
b1.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("OwnerId");
|
||||
|
||||
b1.Property<string>("NickName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("OwnerNickName");
|
||||
|
||||
b1.HasKey("FriendId");
|
||||
|
||||
b1.ToTable("friends");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FriendId");
|
||||
});
|
||||
|
||||
b.OwnsOne("ContactService.Domain.ValueObjects.UserProfile", "Target", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FriendId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b1.Property<string>("Avatar")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("TargetAvatarUrl");
|
||||
|
||||
b1.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("TargetId");
|
||||
|
||||
b1.Property<string>("NickName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("TargetNickName");
|
||||
|
||||
b1.HasKey("FriendId");
|
||||
|
||||
b1.ToTable("friends");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FriendId");
|
||||
});
|
||||
|
||||
b.Navigation("Owner")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Target")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
17
ContactService.Infrastructure/ModuleInit.cs
Normal file
17
ContactService.Infrastructure/ModuleInit.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using ContactService.Domain;
|
||||
using IM.Commons;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ContactService.Infrastructure
|
||||
{
|
||||
public class ModuleInit : IModuleInitializer
|
||||
{
|
||||
public void Initialize(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<FriendDomainService>();
|
||||
services.AddScoped<FriendRequestDomainService>();
|
||||
services.AddScoped<IFriendReposity, FriendReposity>();
|
||||
services.AddScoped<IFriendRequestReposity, FriendRequestReposity>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using ContactService.Domain;
|
||||
|
||||
namespace ContactService.WebApi.Application.Dtos
|
||||
{
|
||||
public class FriendRequestResponse
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
/// <summary>
|
||||
/// 申请人
|
||||
/// </summary>
|
||||
public Guid OwnerId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 被申请人
|
||||
/// </summary>
|
||||
public Guid TargetId { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 申请附言
|
||||
/// </summary>
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑)
|
||||
/// </summary>
|
||||
public FriendRequestStatus State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? RemarkName { get; private set; }
|
||||
public DateTimeOffset CreationTime { get; private set; }
|
||||
public DateTimeOffset? Deletion { get; private set; }
|
||||
|
||||
public DateTimeOffset? ModificationTime { get; private set; }
|
||||
}
|
||||
}
|
||||
20
ContactService.WebApi/Application/Dtos/FriendResonse.cs
Normal file
20
ContactService.WebApi/Application/Dtos/FriendResonse.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using ContactService.Domain;
|
||||
|
||||
namespace ContactService.WebApi.Application.Dtos
|
||||
{
|
||||
public class FriendResonse
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public Guid TargetId { get; private set; }
|
||||
public string? Avatar { get; private set; }
|
||||
public string NickName { get; private set; }
|
||||
/// <summary>
|
||||
/// 好友备注名
|
||||
/// </summary>
|
||||
public string? RemarkName { get; private set; }
|
||||
|
||||
public DateTime CreateTime { get; private set; }
|
||||
public DateTime? UpdateTime { get; private set; }
|
||||
public FriendStatus Status { get; private set; }
|
||||
}
|
||||
}
|
||||
16
ContactService.WebApi/Application/Dtos/UserInfoDto.cs
Normal file
16
ContactService.WebApi/Application/Dtos/UserInfoDto.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace ContactService.WebApi.Application.Dtos
|
||||
{
|
||||
public class UserInfoDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string NickName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string Region { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public DateTimeOffset CreationTime { get; set; }
|
||||
public DateTimeOffset? Deletion { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
using ContactService.Domain.Events;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.WebApi.Application.EventHandler
|
||||
{
|
||||
public class FriendAddedHandler : INotificationHandler<FriendAddedDomainEvent>
|
||||
{
|
||||
private readonly IPublishEndpoint endpoint;
|
||||
|
||||
public FriendAddedHandler(IPublishEndpoint endpoint)
|
||||
{
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public async Task Handle(FriendAddedDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
await endpoint.Publish(new FriendAddedEvent
|
||||
{
|
||||
OwnerAvatar = notification.Friend.Owner.Avatar,
|
||||
OwnerId = notification.Friend.Owner.Id,
|
||||
OwnerNickName = notification.Friend.Owner.NickName,
|
||||
TargetAvatar = notification.Friend.Target.Avatar,
|
||||
TargetId = notification.Friend.Target.Id,
|
||||
TargetNickName = notification.Friend.Target.NickName,
|
||||
RemarkName = notification.Friend.RemarkName,
|
||||
Status = notification.Friend.Status.ToString(),
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using ContactService.Domain;
|
||||
using ContactService.Domain.Events;
|
||||
using ContactService.Domain.ValueObjects;
|
||||
using ContactService.Infrastructure;
|
||||
using ContactService.WebApi.Application.IntegrationServices;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
|
||||
namespace ContactService.WebApi.Application.EventHandler
|
||||
{
|
||||
public class FriendRequestStatusUpdateHandler : INotificationHandler<FriendRequestStateUpdateDomainEvent>
|
||||
{
|
||||
private readonly FriendDomainService friendService;
|
||||
private readonly ContactDbContext contactDb;
|
||||
private readonly IPublishEndpoint endpoint;
|
||||
private readonly IIdentityIntegrationService idService;
|
||||
|
||||
public FriendRequestStatusUpdateHandler(FriendDomainService friendService, ContactDbContext contactDb, IPublishEndpoint endpoint, IIdentityIntegrationService idService)
|
||||
{
|
||||
this.friendService = friendService;
|
||||
this.contactDb = contactDb;
|
||||
this.endpoint = endpoint;
|
||||
this.idService = idService;
|
||||
}
|
||||
|
||||
public async Task Handle(FriendRequestStateUpdateDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var @event = notification.Request;
|
||||
if (@event.State == FriendRequestStatus.Passed)
|
||||
{
|
||||
var ownerInfo = await idService.FindUserByIdAsync(@event.OwnerId);
|
||||
var targetInfo = await idService.FindUserByIdAsync(@event.TargetId);
|
||||
|
||||
if (!ownerInfo.Succeeded || !targetInfo.Succeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ownerProfile = new UserProfile(ownerInfo.Data.Id, ownerInfo.Data.NickName, ownerInfo.Data.Avatar);
|
||||
var targetProfile = new UserProfile(targetInfo.Data.Id, targetInfo.Data.NickName, targetInfo.Data.Avatar);
|
||||
|
||||
await friendService.CreateAsync(ownerProfile, targetProfile, @event.RemarkName);
|
||||
await friendService.CreateAsync(targetProfile, ownerProfile, notification.AcceptRemarkName);
|
||||
await contactDb.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await endpoint.Publish(new FriendRequestStateUpdateEvent
|
||||
{
|
||||
CorrelationId = @event.TargetId,
|
||||
Description = @event.Description,
|
||||
OwnerId = @event.OwnerId,
|
||||
RemarkName = @event.RemarkName,
|
||||
State = @event.State.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using ContactService.Domain;
|
||||
using ContactService.Infrastructure;
|
||||
using IM.Commons.IntegrationEvents;
|
||||
using MassTransit;
|
||||
|
||||
namespace ContactService.WebApi.Application.EventHandler
|
||||
{
|
||||
public class UserProfileUpdateHandler : IConsumer<UserProfileUpdateEvent>
|
||||
{
|
||||
private readonly ContactDbContext contactDb;
|
||||
private readonly IFriendReposity reposity;
|
||||
|
||||
public UserProfileUpdateHandler(ContactDbContext contactDb, IFriendReposity reposity)
|
||||
{
|
||||
this.contactDb = contactDb;
|
||||
this.reposity = reposity;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<UserProfileUpdateEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
var friends = await reposity.FindByTargetAsync(@event.UserId);
|
||||
|
||||
foreach (var friend in friends)
|
||||
{
|
||||
friend.UpdateUserInfo(
|
||||
new Domain.ValueObjects.UserProfile(
|
||||
@event.UserId, @event.Avatar, @event.NickName));
|
||||
}
|
||||
|
||||
await contactDb.SaveChangesAsync();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
using AutoMapper;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace ContactService.WebApi.Application.Friend
|
||||
{
|
||||
public class FriendMapperConfig : Profile
|
||||
{
|
||||
public FriendMapperConfig()
|
||||
{
|
||||
CreateMap<Domain.Entities.Friend, FriendResonse>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
|
||||
.ForMember(dest => dest.Avatar, opt => opt.MapFrom(src => src.Target.Avatar))
|
||||
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status))
|
||||
.ForMember(dest => dest.UpdateTime, opt => opt.MapFrom(src => src.ModificationTime.Value.DateTime))
|
||||
.ForMember(dest => dest.CreateTime, opt => opt.MapFrom(src => src.CreationTime.DateTime))
|
||||
.ForMember(dest => dest.NickName, opt => opt.MapFrom(src => src.Target.NickName))
|
||||
.ForMember(dest => dest.RemarkName, opt => opt.MapFrom(src => src.RemarkName))
|
||||
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.Target.Id))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
ContactService.WebApi/Application/Friend/FriendService.cs
Normal file
73
ContactService.WebApi/Application/Friend/FriendService.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using AutoMapper;
|
||||
using ContactService.Domain;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using ContactService.WebApi.Application.IntegrationServices;
|
||||
using IM.Commons;
|
||||
|
||||
namespace ContactService.WebApi.Application.Friend
|
||||
{
|
||||
public class FriendService
|
||||
{
|
||||
private readonly IFriendReposity reposity;
|
||||
private readonly FriendDomainService service;
|
||||
private readonly IIdentityIntegrationService idService;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public FriendService(IFriendReposity reposity, FriendDomainService service
|
||||
, IIdentityIntegrationService idService, IMapper mapper
|
||||
)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.service = service;
|
||||
this.idService = idService;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Result<List<FriendResonse>>> GetFriendsByOwnerIdAsync(Guid ownerId)
|
||||
{
|
||||
IEnumerable<Domain.Entities.Friend> friend = await reposity.FindByOwnerAsync(ownerId);
|
||||
return Result<List<FriendResonse>>.Success(mapper.Map<List<FriendResonse>>(friend.ToList()));
|
||||
}
|
||||
|
||||
public async Task<Result<object>> DeleteFriendAsync(Guid userId, Guid friendId)
|
||||
{
|
||||
var friend = await reposity.FindByIdAsync(friendId);
|
||||
if (friend is null)
|
||||
{
|
||||
return Result<object>.Fail(ResultCode.FRIEND_RELATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (friend.Owner.Id != userId)
|
||||
{
|
||||
return Result<object>.Fail(ResultCode.FRIEND_RELATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
friend.SoftDelete();
|
||||
|
||||
return Result<object>.Success();
|
||||
}
|
||||
|
||||
public async Task<Result<object>> BlockFriendAsync(Guid userId, Guid friendId)
|
||||
{
|
||||
var friend = await reposity.FindByIdAsync(friendId);
|
||||
if (friend is null)
|
||||
{
|
||||
return Result<object>.Fail(ResultCode.FRIEND_RELATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (friend.Owner.Id != userId)
|
||||
{
|
||||
return Result<object>.Fail(ResultCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
friend.Block();
|
||||
return Result<object>.Success();
|
||||
}
|
||||
|
||||
public async Task<Result<bool>> CheckFriendAsync(Guid ownerId, Guid targetId)
|
||||
{
|
||||
var exist = await reposity.CheckOwnerIdAndTargetIdAsync(ownerId, targetId);
|
||||
return Result.Success(exist);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace ContactService.WebApi.Application.FriendRequest
|
||||
{
|
||||
public record CreateFriendRequestCommand
|
||||
{
|
||||
public Guid ownerId { get; private set; }
|
||||
public Guid targetId { get; private set; }
|
||||
public string? description { get; private set; }
|
||||
public string? remarkName { get; private set; }
|
||||
|
||||
public CreateFriendRequestCommand(Guid ownerId, Guid targetId, string? description, string? remarkName)
|
||||
{
|
||||
this.ownerId = ownerId;
|
||||
this.targetId = targetId;
|
||||
this.description = description;
|
||||
this.remarkName = remarkName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using AutoMapper;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
|
||||
namespace ContactService.WebApi.Application.FriendRequest
|
||||
{
|
||||
public class FriendRequestConfig : Profile
|
||||
{
|
||||
public FriendRequestConfig()
|
||||
{
|
||||
CreateMap<Domain.Entities.FriendRequest, FriendRequestResponse>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
namespace ContactService.WebApi.Application.FriendRequest
|
||||
{
|
||||
public class FriendRequestHandleCommand
|
||||
{
|
||||
public Guid UserId { get; private set; }
|
||||
public Guid RequestId { get; private set; }
|
||||
public FriendRequestAction Action { get; private set; }
|
||||
public string? RemarkName { get; set; }
|
||||
|
||||
public FriendRequestHandleCommand(Guid userId, Guid requestId, FriendRequestAction action, string? remarkName)
|
||||
{
|
||||
UserId = userId;
|
||||
RequestId = requestId;
|
||||
Action = action;
|
||||
RemarkName = remarkName;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FriendRequestAction
|
||||
{
|
||||
Accpet = 0,
|
||||
Reject = 1,
|
||||
Block = 2
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
using AutoMapper;
|
||||
using ContactService.Domain;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using IM.Commons;
|
||||
|
||||
namespace ContactService.WebApi.Application.FriendRequest
|
||||
{
|
||||
public class FriendRequestService
|
||||
{
|
||||
private readonly IFriendRequestReposity reposity;
|
||||
private readonly FriendRequestDomainService service;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public FriendRequestService(IFriendRequestReposity reposity, FriendRequestDomainService service, IMapper mapper)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.service = service;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Result<FriendRequestResponse>> CreateAsync(CreateFriendRequestCommand command)
|
||||
{
|
||||
var request = await service.CreateAsync(command.ownerId, command.targetId, command.description, command.remarkName);
|
||||
if (!request.Succeeded)
|
||||
{
|
||||
return Result<FriendRequestResponse>.Fail(request);
|
||||
}
|
||||
return Result<FriendRequestResponse>.Success(mapper.Map<FriendRequestResponse>(request.Data));
|
||||
}
|
||||
|
||||
public async Task<Result<FriendRequestResponse>> UpdateStatusAsync(FriendRequestHandleCommand command)
|
||||
{
|
||||
var request = await reposity.FindByIdAsync(command.RequestId);
|
||||
if (request == null)
|
||||
{
|
||||
return Result<FriendRequestResponse>.Fail(ResultCode.FRIEND_REQUEST_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (request.TargetId != command.UserId)
|
||||
{
|
||||
return Result<FriendRequestResponse>.Fail(ResultCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
switch (command.Action)
|
||||
{
|
||||
case FriendRequestAction.Accpet:
|
||||
request.Accept(command.RemarkName);
|
||||
break;
|
||||
case FriendRequestAction.Block:
|
||||
request.Block();
|
||||
break;
|
||||
case FriendRequestAction.Reject:
|
||||
request.Reject();
|
||||
break;
|
||||
default:
|
||||
return Result<FriendRequestResponse>.Fail(ResultCode.PARAMETER_ERROR);
|
||||
}
|
||||
return Result<FriendRequestResponse>.Success(mapper.Map<FriendRequestResponse>(request));
|
||||
}
|
||||
|
||||
public async Task<Result<List<FriendRequestResponse>>> GetByOwnerIdAsync(Guid ownerId)
|
||||
{
|
||||
var requests = await reposity.FindByOwnerIdAsync(ownerId);
|
||||
return Result<List<FriendRequestResponse>>.Success(mapper.Map<List<FriendRequestResponse>>(requests));
|
||||
|
||||
}
|
||||
|
||||
public async Task<Result<List<FriendRequestResponse>>> GetByTargetIdAsync(Guid targetId)
|
||||
{
|
||||
var requests = await reposity.FindByTargetIdAsync(targetId);
|
||||
return Result<List<FriendRequestResponse>>.Success(mapper.Map<List<FriendRequestResponse>>(requests));
|
||||
}
|
||||
|
||||
public async Task<Result<List<FriendRequestResponse>>> GetByTargetIdOrOwnerIdAsync(Guid id)
|
||||
{
|
||||
var requests = await reposity.FindByTargetIdAsync(id);
|
||||
var requests2 = await reposity.FindByOwnerIdAsync(id);
|
||||
return Result<List<FriendRequestResponse>>.Success(mapper.Map<List<FriendRequestResponse>>(requests.Concat(requests2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using IM.Commons;
|
||||
|
||||
namespace ContactService.WebApi.Application.IntegrationServices
|
||||
{
|
||||
public interface IIdentityIntegrationService
|
||||
{
|
||||
Task<Result<UserInfoDto>> FindUserByIdAsync(Guid id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using Grpc.Core;
|
||||
using IM.Commons;
|
||||
using IM.Protocols.Grpc.User;
|
||||
|
||||
namespace ContactService.WebApi.Application.IntegrationServices
|
||||
{
|
||||
public class IdentityIntegrationService : IIdentityIntegrationService
|
||||
{
|
||||
private readonly UserInternal.UserInternalClient client;
|
||||
|
||||
public IdentityIntegrationService(UserInternal.UserInternalClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async Task<Result<UserInfoDto>> FindUserByIdAsync(Guid id)
|
||||
{
|
||||
var req = new GetUserInfoRequest()
|
||||
{
|
||||
UserId = id.ToString()
|
||||
};
|
||||
try
|
||||
{
|
||||
var res = await client.GetUserInfoAsyncAsync(req);
|
||||
return Result.Success(new UserInfoDto
|
||||
{
|
||||
Avatar = res.Avatar,
|
||||
CreationTime = res.CreationTime.ToDateTimeOffset(),
|
||||
Deletion = res.Deletion.ToDateTimeOffset(),
|
||||
Description = res.Description,
|
||||
Email = res.Email,
|
||||
Id = Guid.Parse(res.Id),
|
||||
NickName = res.NickName,
|
||||
Phone = res.Phone,
|
||||
Region = res.Region,
|
||||
UserName = res.UserName
|
||||
});
|
||||
}catch(RpcException e)
|
||||
{
|
||||
return Result.Fail<UserInfoDto>(ResultCode.USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
26
ContactService.WebApi/ContactService.WebApi.csproj
Normal file
26
ContactService.WebApi/ContactService.WebApi.csproj
Normal file
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\ContactService.Domain\ContactService.Domain.csproj" />
|
||||
<ProjectReference Include="..\ContactService.Infrastructure\ContactService.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\IM.ASPNETCore\IM.ASPNETCore.csproj" />
|
||||
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
|
||||
<ProjectReference Include="..\IM.Protocols\IM.Protocols.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
7
ContactService.WebApi/ContactService.WebApi.http
Normal file
7
ContactService.WebApi/ContactService.WebApi.http
Normal file
@ -0,0 +1,7 @@
|
||||
@ContactService.WebApi_HostAddress = http://localhost:5294
|
||||
|
||||
GET {{ContactService.WebApi_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
###
|
||||
|
||||
GET {{ContactService.WebApi_HostAddress}}
|
||||
55
ContactService.WebApi/Controllers/FriendController.cs
Normal file
55
ContactService.WebApi/Controllers/FriendController.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using ContactService.Infrastructure;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using ContactService.WebApi.Application.Friend;
|
||||
using IM.ASPNETCore;
|
||||
using IM.Commons;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace ContactService.WebApi.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class FriendController : ControllerBase
|
||||
{
|
||||
private readonly FriendService service;
|
||||
|
||||
public FriendController(FriendService service)
|
||||
{
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesDefaultResponseType(typeof(Result<FriendResonse>))]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var friends = await service.GetFriendsByOwnerIdAsync(Guid.Parse(userId));
|
||||
return Ok(friends);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(ContactDbContext))]
|
||||
public async Task<IActionResult> Delete([FromQuery] Guid friendId)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.DeleteFriendAsync(Guid.Parse(userId), friendId));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(ContactDbContext))]
|
||||
public async Task<IActionResult> Block([FromQuery] Guid friendId)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.BlockFriendAsync(Guid.Parse(userId), friendId));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> CheckFriend(Guid userId, Guid targetId)
|
||||
{
|
||||
return Ok(await service.CheckFriendAsync(userId, targetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
24
ContactService.WebApi/Controllers/FriendRequestAddRequest.cs
Normal file
24
ContactService.WebApi/Controllers/FriendRequestAddRequest.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace ContactService.WebApi.Controllers
|
||||
{
|
||||
public class FriendRequestAddRequest
|
||||
{
|
||||
public Guid TargetId { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? RemarkName { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class FriendRequestAddRequestValidator : AbstractValidator<FriendRequestAddRequest>
|
||||
{
|
||||
public FriendRequestAddRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.TargetId)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
48
ContactService.WebApi/Controllers/FriendRequestController.cs
Normal file
48
ContactService.WebApi/Controllers/FriendRequestController.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using ContactService.Infrastructure;
|
||||
using ContactService.WebApi.Application.Dtos;
|
||||
using ContactService.WebApi.Application.FriendRequest;
|
||||
using IM.ASPNETCore;
|
||||
using IM.Commons;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace ContactService.WebApi.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class FriendRequestController : ControllerBase
|
||||
{
|
||||
private readonly FriendRequestService service;
|
||||
|
||||
public FriendRequestController(FriendRequestService service)
|
||||
{
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(ContactDbContext))]
|
||||
[ProducesDefaultResponseType(typeof(Result<FriendRequestResponse?>))]
|
||||
public async Task<IActionResult> Add(FriendRequestAddRequest request)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.CreateAsync(new CreateFriendRequestCommand(Guid.Parse(userId), request.TargetId, request.Description, request.RemarkName)));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(ContactDbContext))]
|
||||
public async Task<IActionResult> Handle(FriendRequestHandleRequest request)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.UpdateStatusAsync(new FriendRequestHandleCommand(Guid.Parse(userId), request.RequestId, request.Action,request.RemarkName)));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetByTargetIdOrOwnerIdAsync(Guid.Parse(userId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
using ContactService.WebApi.Application.FriendRequest;
|
||||
using FluentValidation;
|
||||
|
||||
namespace ContactService.WebApi.Controllers
|
||||
{
|
||||
public class FriendRequestHandleRequest
|
||||
{
|
||||
public Guid RequestId { get; set; }
|
||||
public FriendRequestAction Action { get; set; }
|
||||
public string? RemarkName { get; set; }
|
||||
}
|
||||
|
||||
public class FriendRequestHandleRequestValidator : AbstractValidator<FriendRequestHandleRequest>
|
||||
{
|
||||
public FriendRequestHandleRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.RequestId)
|
||||
.NotEmpty()
|
||||
.NotNull();
|
||||
|
||||
When(w => w.Action == FriendRequestAction.Accpet, () =>
|
||||
{
|
||||
RuleFor(r => r.RemarkName)
|
||||
.NotEmpty()
|
||||
.NotNull();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
18
ContactService.WebApi/DesignTimeDbContextFactory.cs
Normal file
18
ContactService.WebApi/DesignTimeDbContextFactory.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using ContactService.Infrastructure;
|
||||
using IM.InitCommon;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace ContactService.WebApi
|
||||
{
|
||||
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ContactDbContext>
|
||||
{
|
||||
public ContactDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
// 1. 复用你写好的配置工厂,提取连接字符串
|
||||
var optionsBuilder = DbContextOptionsBuilderFactory.Create<ContactDbContext>();
|
||||
|
||||
// 2. 🌟 关键补刀:把假的 Mediator 传进去,满足构造函数的要求!
|
||||
return new ContactDbContext(optionsBuilder.Options, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ContactService.WebApi/Dockerfile
Normal file
36
ContactService.WebApi/Dockerfile
Normal file
@ -0,0 +1,36 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY IM_API_NEW.sln ./
|
||||
|
||||
COPY ContactService.WebApi/ContactService.WebApi.csproj ContactService.WebApi/
|
||||
COPY ContactService.Domain/ContactService.Domain.csproj ContactService.Domain/
|
||||
COPY ContactService.Infrastructure/ContactService.Infrastructure.csproj ContactService.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 ContactService.WebApi/ContactService.WebApi.csproj
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN dotnet publish ContactService.WebApi/ContactService.WebApi.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "ContactService.WebApi.dll"]
|
||||
24
ContactService.WebApi/ModuleInit.cs
Normal file
24
ContactService.WebApi/ModuleInit.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using ContactService.WebApi.Application.Friend;
|
||||
using ContactService.WebApi.Application.FriendRequest;
|
||||
using ContactService.WebApi.Application.IntegrationServices;
|
||||
using IM.Commons;
|
||||
using IM.Protocols.Grpc.User;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ContactService.WebApi
|
||||
{
|
||||
public class ModuleInit : IModuleInitializer
|
||||
{
|
||||
public void Initialize(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<FriendService>();
|
||||
services.AddScoped<FriendRequestService>();
|
||||
services.AddScoped<IIdentityIntegrationService, IdentityIntegrationService>();
|
||||
services.AddGrpcClient<UserInternal.UserInternalClient>((sp, o) =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptionsMonitor<GrpcOptions>>();
|
||||
o.Address = new Uri(options.CurrentValue.IdentityServiceUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
42
ContactService.WebApi/Program.cs
Normal file
42
ContactService.WebApi/Program.cs
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
using IM.InitCommon;
|
||||
|
||||
namespace ContactService.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();
|
||||
|
||||
builder.Services.AddAllGrpcServer();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseAppDefault();
|
||||
|
||||
|
||||
app.MapControllers();
|
||||
app.MapAllGrpcServer();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
ContactService.WebApi/Properties/launchSettings.json
Normal file
49
ContactService.WebApi/Properties/launchSettings.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5294"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7242;http://localhost:5294"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:46536",
|
||||
"sslPort": 44317
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iissettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:53560/",
|
||||
"sslPort": 44389
|
||||
}
|
||||
}
|
||||
}
|
||||
35
ContactService.WebApi/Services/ContactintegrationService.cs
Normal file
35
ContactService.WebApi/Services/ContactintegrationService.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using ContactService.WebApi.Application.Friend;
|
||||
using Grpc.Core;
|
||||
using IM.Protocols.Grpc.Contact;
|
||||
|
||||
namespace ContactService.WebApi.Services
|
||||
{
|
||||
public class ContactintegrationService: ContactInternal.ContactInternalBase
|
||||
{
|
||||
private readonly Application.Friend.FriendService friendService;
|
||||
|
||||
public ContactintegrationService(FriendService friendService)
|
||||
{
|
||||
this.friendService = friendService;
|
||||
}
|
||||
|
||||
public override async Task<CheckFriendshipResponse> CheckFriendship(CheckFriendshipRequest request, ServerCallContext context)
|
||||
{
|
||||
var response = new CheckFriendshipResponse();
|
||||
var res = await friendService.CheckFriendAsync(
|
||||
Guid.Parse(request.OwnerId),
|
||||
Guid.Parse(request.TargetId)
|
||||
);
|
||||
|
||||
if(res.Succeeded && res.Data)
|
||||
{
|
||||
response.Checked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Checked = false;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
ContactService.WebApi/appsettings.Development.json
Normal file
8
ContactService.WebApi/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
ContactService.WebApi/appsettings.json
Normal file
9
ContactService.WebApi/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
24
DomainCommons/AggregateRootEntity.cs
Normal file
24
DomainCommons/AggregateRootEntity.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public class AggregateRootEntity : BaseEntity, IAggregateRoot, ISoftDelete, IHasCreationTime, IHasDeletionTime, IHasModificationTime
|
||||
{
|
||||
public bool IsDeleted { get; private set; }
|
||||
|
||||
public DateTimeOffset CreationTime { get; private set; } = DateTime.Now;
|
||||
|
||||
public DateTimeOffset? Deletion { get; private set; }
|
||||
|
||||
public DateTimeOffset? ModificationTime { get; set; }
|
||||
|
||||
public void SoftDelete()
|
||||
{
|
||||
Deletion = DateTime.Now;
|
||||
IsDeleted = true;
|
||||
}
|
||||
|
||||
public void NotifyModified()
|
||||
{
|
||||
ModificationTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
DomainCommons/BaseEntity.cs
Normal file
40
DomainCommons/BaseEntity.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public class BaseEntity : IEntity, IDomainEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// 这里使用连续guid,防止数据库性能问题
|
||||
/// </summary>
|
||||
public Guid Id { get; private set; } = NewId.NextGuid();
|
||||
|
||||
[NotMapped]
|
||||
public List<INotification> domainEvents = [];
|
||||
|
||||
public void AddDomainEvent(INotification eventItem)
|
||||
{
|
||||
domainEvents.Add(eventItem);
|
||||
}
|
||||
|
||||
public void AddDomainEventIfAbsent(INotification eventItem)
|
||||
{
|
||||
if (!domainEvents.Contains(eventItem))
|
||||
{
|
||||
domainEvents.Add(eventItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearDomainEvents()
|
||||
{
|
||||
domainEvents.Clear();
|
||||
}
|
||||
|
||||
public IEnumerable<INotification> GetDomainEvents()
|
||||
{
|
||||
return domainEvents;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
DomainCommons/DomainException.cs
Normal file
10
DomainCommons/DomainException.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public class DomainException : Exception
|
||||
{
|
||||
public DomainException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
6
DomainCommons/IAggregateRoot.cs
Normal file
6
DomainCommons/IAggregateRoot.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IAggregateRoot
|
||||
{
|
||||
}
|
||||
}
|
||||
12
DomainCommons/IDomainEvents.cs
Normal file
12
DomainCommons/IDomainEvents.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using MediatR;
|
||||
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IDomainEvents
|
||||
{
|
||||
IEnumerable<INotification> GetDomainEvents();
|
||||
void AddDomainEvent(INotification eventItem);
|
||||
void AddDomainEventIfAbsent(INotification eventItem);
|
||||
void ClearDomainEvents();
|
||||
}
|
||||
}
|
||||
7
DomainCommons/IEntity.cs
Normal file
7
DomainCommons/IEntity.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IEntity
|
||||
{
|
||||
Guid Id { get; }
|
||||
}
|
||||
}
|
||||
7
DomainCommons/IHasCreationTime.cs
Normal file
7
DomainCommons/IHasCreationTime.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IHasCreationTime
|
||||
{
|
||||
DateTimeOffset CreationTime { get; }
|
||||
}
|
||||
}
|
||||
7
DomainCommons/IHasDeletionTime.cs
Normal file
7
DomainCommons/IHasDeletionTime.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IHasDeletionTime
|
||||
{
|
||||
DateTimeOffset? Deletion { get; }
|
||||
}
|
||||
}
|
||||
7
DomainCommons/IHasModificationTime.cs
Normal file
7
DomainCommons/IHasModificationTime.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface IHasModificationTime
|
||||
{
|
||||
DateTimeOffset? ModificationTime { get; }
|
||||
}
|
||||
}
|
||||
14
DomainCommons/IM.DomainCommons.csproj
Normal file
14
DomainCommons/IM.DomainCommons.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="14.1.0" />
|
||||
<PackageReference Include="NewId" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
8
DomainCommons/ISoftDelete.cs
Normal file
8
DomainCommons/ISoftDelete.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace IM.DomainCommons
|
||||
{
|
||||
public interface ISoftDelete
|
||||
{
|
||||
bool IsDeleted { get; }
|
||||
void SoftDelete();
|
||||
}
|
||||
}
|
||||
19
FileService.Application/FileService.Application.csproj
Normal file
19
FileService.Application/FileService.Application.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FileService.Domain\FileService.Domain.csproj" />
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
FileService.Application/ModueInit.cs
Normal file
14
FileService.Application/ModueInit.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using FileService.Application.UploadFile;
|
||||
using IM.Commons;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileService.Application
|
||||
{
|
||||
public class ModueInit : IModuleInitializer
|
||||
{
|
||||
public void Initialize(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<UploadFileService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
FileService.Application/Ports/IObjectStoragePort.cs
Normal file
12
FileService.Application/Ports/IObjectStoragePort.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using FileService.Application.StorageContracts;
|
||||
|
||||
namespace FileService.Application.Ports
|
||||
{
|
||||
public interface IObjectStoragePort
|
||||
{
|
||||
string ProviderCode { get; }
|
||||
public Task<InitiateUploadResult> InitUploadAsync(InitiateUploadCommand command,CancellationToken token);
|
||||
public Task<PresignedUrl> GenerateUploadUrlAsync(GenerateUploadUrlCommand command, CancellationToken token);
|
||||
public Task<CompleteUploadResult> CompleteUploadAsync(CompleteUploadCommand command, CancellationToken token);
|
||||
}
|
||||
}
|
||||
13
FileService.Application/Ports/IObjectStorageRouter.cs
Normal file
13
FileService.Application/Ports/IObjectStorageRouter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.Ports
|
||||
{
|
||||
public interface IObjectStorageRouter
|
||||
{
|
||||
IObjectStoragePort Route(string providerCode);
|
||||
}
|
||||
}
|
||||
12
FileService.Application/Ports/IStorageRedisCache.cs
Normal file
12
FileService.Application/Ports/IStorageRedisCache.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using FileService.Application.StorageContracts;
|
||||
|
||||
namespace FileService.Application.Ports
|
||||
{
|
||||
public interface IStorageRedisCache
|
||||
{
|
||||
Task SetAsync(UploadRuntimeCache upload);
|
||||
Task DeleteAsync(string sessionId);
|
||||
Task DeleteByTaskIdAsync(string taskId);
|
||||
Task<UploadRuntimeCache?> GetAsync(string sessionId);
|
||||
}
|
||||
}
|
||||
69
FileService.Application/StorageContracts/StorageDto.cs
Normal file
69
FileService.Application/StorageContracts/StorageDto.cs
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
using FileService.Domain.ValueObjects;
|
||||
|
||||
namespace FileService.Application.StorageContracts
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ProviderCode">储存提供商编号</param>
|
||||
/// <param name="Bucket">储存桶名称</param>
|
||||
/// <param name="ObjectKey">鉴权key</param>
|
||||
/// <param name="ContentType"></param>
|
||||
/// <param name="ContentLength"></param>
|
||||
/// <param name="Metadata"></param>
|
||||
public sealed record InitiateUploadCommand(
|
||||
string ProviderCode,
|
||||
string Bucket,
|
||||
string ObjectKey,
|
||||
string ContentType,
|
||||
long ContentLength,
|
||||
IReadOnlyDictionary<string, string>? Metadata = null);
|
||||
|
||||
public sealed record InitiateUploadResult(
|
||||
string UploadSessionId,
|
||||
StorageLocation Location);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ProviderCode"></param>
|
||||
/// <param name="Bucket"></param>
|
||||
/// <param name="ObjectKey"></param>
|
||||
/// <param name="UploadSessionId"></param>
|
||||
/// <param name="PartNumber"></param>
|
||||
/// <param name="ExpiresIn"></param>
|
||||
public sealed record GenerateUploadUrlCommand(
|
||||
string ProviderCode,
|
||||
string Bucket,
|
||||
string ObjectKey,
|
||||
string UploadSessionId,
|
||||
int? PartNumber,
|
||||
TimeSpan ExpiresIn);
|
||||
|
||||
public sealed record PresignedUrl(
|
||||
string Url,
|
||||
IReadOnlyDictionary<string, string> Headers,
|
||||
DateTimeOffset ExpiresAt);
|
||||
|
||||
public sealed record CompleteUploadCommand(
|
||||
string ProviderCode,
|
||||
string Bucket,
|
||||
string Region,
|
||||
string ObjectKey,
|
||||
string UploadSessionId,
|
||||
IReadOnlyList<UploadPart> Parts);
|
||||
|
||||
public sealed record UploadPart(
|
||||
int PartNumber,
|
||||
string ETag,
|
||||
long? Size = null,
|
||||
string? Checksum = null);
|
||||
|
||||
public sealed record CompleteUploadResult(
|
||||
StorageLocation Location,
|
||||
string? ETag,
|
||||
long Size,
|
||||
string? Checksum = null,
|
||||
string? VersionId = null);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.StorageContracts
|
||||
{
|
||||
public sealed class UploadRuntimeCache
|
||||
{
|
||||
public string TaskId { get; init; } = default!;
|
||||
|
||||
public string ProviderCode { get; init; } = default!;
|
||||
|
||||
public string UploadSessionId { get; init; } = default!;
|
||||
|
||||
public string Bucket { get; init; } = default!;
|
||||
|
||||
public string Region { get; init; } = default!;
|
||||
|
||||
public string ObjectKey { get; init; } = default!;
|
||||
|
||||
public long FileSize { get; init; }
|
||||
|
||||
public int TotalPartCount { get; init; }
|
||||
|
||||
public long UploadedBytes { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
public DateTimeOffset ExpireAt { get; init; }
|
||||
|
||||
public Dictionary<int, UploadPart> Parts { get; init; } = new();
|
||||
|
||||
public void AddOrUpdatePart(UploadPart part)
|
||||
{
|
||||
Parts[part.PartNumber] = part;
|
||||
|
||||
UploadedBytes = Parts.Sum(x => x.Value.Size ?? 0);
|
||||
}
|
||||
|
||||
public bool IsCompleted()
|
||||
{
|
||||
return Parts.Count == TotalPartCount;
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
|
||||
public static UploadRuntimeCache? FromJson(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<UploadRuntimeCache>(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
FileService.Application/UploadFile/FileResponse.cs
Normal file
24
FileService.Application/UploadFile/FileResponse.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using FileService.Domain;
|
||||
using FileService.Domain.ValueObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFile
|
||||
{
|
||||
public class FileResponse
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OwnerId { get; set; }
|
||||
public FileName FileName { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public ContentType ContentType { get; set; }
|
||||
public FileState State { get; set; }
|
||||
public StorageLocation StorageLocation { get; set; }
|
||||
public CheckSum CheckSum { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public DateTimeOffset Updated { get; set;
|
||||
}
|
||||
}
|
||||
20
FileService.Application/UploadFile/UploadFileMapperConfig.cs
Normal file
20
FileService.Application/UploadFile/UploadFileMapperConfig.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using AutoMapper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFile
|
||||
{
|
||||
public class UploadFileMapperConfig:Profile
|
||||
{
|
||||
public UploadFileMapperConfig()
|
||||
{
|
||||
CreateMap<Domain.Entities.UploadFile, FileResponse>()
|
||||
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.CreationTime))
|
||||
.ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.ModificationTime))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
FileService.Application/UploadFile/UploadFileService.cs
Normal file
29
FileService.Application/UploadFile/UploadFileService.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using AutoMapper;
|
||||
using FileService.Domain.IReposities;
|
||||
using IM.Commons;
|
||||
|
||||
namespace FileService.Application.UploadFile
|
||||
{
|
||||
public class UploadFileService
|
||||
{
|
||||
private readonly IUploadFileReposity reposity;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public UploadFileService(IUploadFileReposity reposity, IMapper mapper)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Result<FileResponse>> GetFileInfoAsync(Guid id)
|
||||
{
|
||||
var file = await reposity.FindByIdAsync(id);
|
||||
if (file == null)
|
||||
{
|
||||
return Result.Fail<FileResponse>(ResultCode.FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
return Result.Success(mapper.Map<FileResponse>(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
17
FileService.Application/UploadFileTask/TaskInitResponse.cs
Normal file
17
FileService.Application/UploadFileTask/TaskInitResponse.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using FileService.Domain.ValueObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFileTask
|
||||
{
|
||||
public class TaskInitResponse
|
||||
{
|
||||
public Guid TaskId { get; set; }
|
||||
public string UploadSessionId { get; init; }
|
||||
|
||||
public StorageLocation StorageLocation { get; init; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
using AutoMapper;
|
||||
using FileService.Application.Ports;
|
||||
using FileService.Domain.Entities;
|
||||
using FileService.Domain.IReposities;
|
||||
using IM.Commons;
|
||||
using IM.InitCommon;
|
||||
using MassTransit.Internals;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFileTask
|
||||
{
|
||||
public class UploadFileTaskService
|
||||
{
|
||||
private readonly IUploadTaskReposity reposity;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IObjectStorageRouter router;
|
||||
private readonly IOptions<StorageOptions> options;
|
||||
|
||||
public UploadFileTaskService(IUploadTaskReposity reposity, IMapper mapper, IObjectStorageRouter router, IOptions<StorageOptions> options)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.mapper = mapper;
|
||||
this.router = router;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public async Task<Result<TaskInitResponse>> InitTaskAsync(UploadTaskInitCommand command)
|
||||
{
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
var task = command.ToUploadTask();
|
||||
var storage = router.Route(options.Value.ProviderCode);
|
||||
var initRes = await storage.InitUploadAsync(new StorageContracts.InitiateUploadCommand(
|
||||
ProviderCode: options.Value.ProviderCode,
|
||||
Bucket: options.Value.Bucket,
|
||||
ObjectKey: options.Value.Endpoint,
|
||||
ContentType:task.ContentType.Value,
|
||||
ContentLength: command.FileSize,
|
||||
null
|
||||
), cancellationToken);
|
||||
|
||||
var res = mapper.Map<TaskInitResponse>(initRes);
|
||||
res.TaskId = task.Id;
|
||||
|
||||
return Result.Success(res);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFileTask
|
||||
{
|
||||
public record UploadTaskInitCommand(
|
||||
Guid UploaderId,
|
||||
Guid ConversationId, string FileName,
|
||||
long FileSize,string contentType,
|
||||
string checkSum
|
||||
)
|
||||
{
|
||||
public Domain.Entities.UploadTask ToUploadTask()
|
||||
{
|
||||
return new Domain.Entities.UploadTask(
|
||||
UploaderId,
|
||||
ConversationId, FileName, FileSize, contentType,
|
||||
null,new Domain.ValueObjects.CheckSum("md5", checkSum)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using AutoMapper;
|
||||
using FileService.Application.StorageContracts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Application.UploadFileTask
|
||||
{
|
||||
public class UploadTaskMapperConfig:Profile
|
||||
{
|
||||
public UploadTaskMapperConfig()
|
||||
{
|
||||
CreateMap<InitiateUploadResult, TaskInitResponse>()
|
||||
.ForMember(dest => dest.StorageLocation, opt => opt.MapFrom(src => src.Location))
|
||||
.ForMember(dest => dest.UploadSessionId, opt => opt.MapFrom(src => src.UploadSessionId))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
FileService.Domain/Entities/UploadFile.cs
Normal file
28
FileService.Domain/Entities/UploadFile.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using FileService.Domain.ValueObjects;
|
||||
using IM.DomainCommons;
|
||||
|
||||
namespace FileService.Domain.Entities
|
||||
{
|
||||
public class UploadFile:AggregateRootEntity
|
||||
{
|
||||
public Guid OwnerId { get; private set; }
|
||||
public FileName FileName { get; private set; }
|
||||
public long FileSize { get;private set; }
|
||||
public ContentType ContentType { get;private set; }
|
||||
public FileState State { get; private set; } = FileState.Uploaded;
|
||||
public StorageLocation StorageLocation { get; private set; } = new StorageLocation();
|
||||
public CheckSum CheckSum { get; private set; }
|
||||
|
||||
private UploadFile() { }
|
||||
|
||||
public UploadFile(Guid ownerId, FileName fileName, long fileSize, ContentType contentType, StorageLocation? storageLocation, CheckSum checkSum)
|
||||
{
|
||||
OwnerId = ownerId;
|
||||
FileName = fileName;
|
||||
FileSize = fileSize;
|
||||
ContentType = contentType;
|
||||
StorageLocation = storageLocation ?? new StorageLocation();
|
||||
CheckSum = checkSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
FileService.Domain/Entities/UploadTask.cs
Normal file
48
FileService.Domain/Entities/UploadTask.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using FileService.Domain.Events;
|
||||
using FileService.Domain.ValueObjects;
|
||||
using IM.DomainCommons;
|
||||
|
||||
namespace FileService.Domain.Entities
|
||||
{
|
||||
public class UploadTask:AggregateRootEntity
|
||||
{
|
||||
public Guid UploaderId { get; private set; }
|
||||
public Guid ConversationId { get; private set; }
|
||||
public FileName FileName { get; private set; }
|
||||
public long FileSize { get; private set; }
|
||||
public ContentType ContentType { get; private set; }
|
||||
public StorageLocation StorageLocation { get; private set; }
|
||||
public UploadTaskState State { get; private set; }
|
||||
public CheckSum CheckSum { get; private set; }
|
||||
|
||||
private UploadTask() { }
|
||||
|
||||
public UploadTask(Guid uploaderId, Guid conversationId, FileName fileName, long fileSize, ContentType contentType, StorageLocation? storageLocation, CheckSum checkSum)
|
||||
{
|
||||
UploaderId = uploaderId;
|
||||
ConversationId = conversationId;
|
||||
FileName = fileName;
|
||||
FileSize = fileSize;
|
||||
ContentType = contentType;
|
||||
StorageLocation = storageLocation ?? new StorageLocation();
|
||||
CheckSum = checkSum;
|
||||
}
|
||||
|
||||
public void StartUpload()
|
||||
{
|
||||
State = UploadTaskState.Uploading;
|
||||
}
|
||||
|
||||
public void CompleteUpload(StorageLocation location)
|
||||
{
|
||||
State = UploadTaskState.Completed;
|
||||
AddDomainEvent(new UploadTaskCompletedDomainEvent(this));
|
||||
}
|
||||
|
||||
public void Fail()
|
||||
{
|
||||
State = UploadTaskState.Failed;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
12
FileService.Domain/Events/UploadTaskCompletedDomainEvent.cs
Normal file
12
FileService.Domain/Events/UploadTaskCompletedDomainEvent.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using FileService.Domain.Entities;
|
||||
using MediatR;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain.Events
|
||||
{
|
||||
public class UploadTaskCompletedDomainEvent(UploadTask task):INotification;
|
||||
}
|
||||
13
FileService.Domain/FileService.Domain.csproj
Normal file
13
FileService.Domain/FileService.Domain.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DomainCommons\IM.DomainCommons.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
17
FileService.Domain/FileState.cs
Normal file
17
FileService.Domain/FileState.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain
|
||||
{
|
||||
public enum FileState
|
||||
{
|
||||
Created = 0,
|
||||
Uploading = 1,
|
||||
Uploaded = 2,
|
||||
Failed = 3,
|
||||
Deleted = 4
|
||||
}
|
||||
}
|
||||
16
FileService.Domain/IReposities/IUploadFileReposity.cs
Normal file
16
FileService.Domain/IReposities/IUploadFileReposity.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using FileService.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain.IReposities
|
||||
{
|
||||
public interface IUploadFileReposity
|
||||
{
|
||||
void Create(UploadFile file);
|
||||
Task<UploadFile?> FindByIdAsync(Guid id);
|
||||
Task<UploadFile?> FindByCheckSumAsync(Guid uploaderId,string value);
|
||||
}
|
||||
}
|
||||
16
FileService.Domain/IReposities/IUploadTaskReposity.cs
Normal file
16
FileService.Domain/IReposities/IUploadTaskReposity.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using FileService.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain.IReposities
|
||||
{
|
||||
public interface IUploadTaskReposity
|
||||
{
|
||||
void Create(UploadTask task);
|
||||
Task<UploadTask?> FindByIdAsync(Guid id);
|
||||
Task<UploadTask?> FindByCheckSumAsync(string value);
|
||||
}
|
||||
}
|
||||
18
FileService.Domain/UploadTaskState.cs
Normal file
18
FileService.Domain/UploadTaskState.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain
|
||||
{
|
||||
public enum UploadTaskState
|
||||
{
|
||||
Created = 0,
|
||||
Failed = 1,
|
||||
Uploading = 2,
|
||||
Uploaded = 4,
|
||||
Merging = 5,
|
||||
Completed = 6,
|
||||
}
|
||||
}
|
||||
30
FileService.Domain/ValueObjects/CheckSum.cs
Normal file
30
FileService.Domain/ValueObjects/CheckSum.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain.ValueObjects
|
||||
{
|
||||
public class CheckSum
|
||||
{
|
||||
public string Algorithm { get; }
|
||||
public string Value { get; }
|
||||
|
||||
public CheckSum(string algorithm, string value)
|
||||
{
|
||||
Algorithm = algorithm;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public static implicit operator CheckSum((string algorithm, string value) value)
|
||||
{
|
||||
return new CheckSum(value.algorithm, value.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
FileService.Domain/ValueObjects/ContentType.cs
Normal file
27
FileService.Domain/ValueObjects/ContentType.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileService.Domain.ValueObjects
|
||||
{
|
||||
public class ContentType
|
||||
{
|
||||
public string Value { get; }
|
||||
public ContentType(string contentType)
|
||||
{
|
||||
Value = contentType;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public static implicit operator ContentType(string contentType)
|
||||
{
|
||||
return new ContentType(contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user