Merge pull request 'feature-nxdev' (#58) from feature-nxdev into main
Reviewed-on: #58
This commit is contained in:
commit
77c71f4017
89
backend/IMTest/Service/GroupServiceTest.cs
Normal file
89
backend/IMTest/Service/GroupServiceTest.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Models;
|
||||
using IM_API.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MassTransit;
|
||||
using IM_API.Configs;
|
||||
|
||||
namespace IM_API.Tests.Services
|
||||
{
|
||||
public class GroupServiceTests
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly Mock<ILogger<GroupService>> _loggerMock = new();
|
||||
private readonly Mock<IPublishEndpoint> _publishMock = new();
|
||||
|
||||
public GroupServiceTests()
|
||||
{
|
||||
// 1. 配置内存数据库
|
||||
var options = new DbContextOptionsBuilder<ImContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
_context = new ImContext(options);
|
||||
|
||||
// 2. 配置真实的 AutoMapper (ProjectTo 必须使用真实配置,不能用 Mock)
|
||||
var config = new MapperConfiguration(cfg =>
|
||||
{
|
||||
// 替换为你项目中真实的 Profile 类名
|
||||
cfg.AddProfile<MapperConfig>();
|
||||
// 如果有多个 Profile,可以继续添加或者加载整个程序集
|
||||
// cfg.AddMaps(typeof(GroupProfile).Assembly);
|
||||
});
|
||||
_mapper = config.CreateMapper();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetGroupList_ShouldReturnPagedAndOrderedData()
|
||||
{
|
||||
// Arrange (准备数据)
|
||||
var userId = 1;
|
||||
var groups = new List<Group>
|
||||
{
|
||||
new Group { Id = 101, Name = "Group A", Avatar = "1111" },
|
||||
new Group { Id = 102, Name = "Group B" , Avatar = "1111"},
|
||||
new Group { Id = 103, Name = "Group C", Avatar = "1111" }
|
||||
};
|
||||
|
||||
_context.Groups.AddRange(groups);
|
||||
_context.GroupMembers.AddRange(new List<GroupMember>
|
||||
{
|
||||
new GroupMember { UserId = userId, GroupId = 101 },
|
||||
new GroupMember { UserId = userId, GroupId = 102 },
|
||||
new GroupMember { UserId = userId, GroupId = 103 }
|
||||
});
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object);
|
||||
|
||||
// Act (执行测试: 第1页,每页2条,倒序)
|
||||
var result = await service.GetGroupListAsync(userId, page: 1, limit: 2, desc: true);
|
||||
|
||||
// Assert (断言)
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal(103, result[0].Id); // 倒序最大的是 103
|
||||
Assert.Equal(102, result[1].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetGroupList_ShouldReturnEmpty_WhenUserHasNoGroups()
|
||||
{
|
||||
// Arrange
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object);
|
||||
|
||||
// Act
|
||||
var result = await service.GetGroupListAsync(userId: 999, page: 1, limit: 10, desc: false);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,6 +250,14 @@
|
||||
"System.Text.Encodings.Web": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/1.1.0": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||
"assemblyVersion": "1.0.0.0",
|
||||
"fileVersion": "4.700.19.56404"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeCoverage/17.8.0": {
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {
|
||||
@ -900,6 +908,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "1.1.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.1",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
"StackExchange.Redis": "2.9.32"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/RedLockNet.Abstractions.dll": {
|
||||
"assemblyVersion": "2.3.2.0",
|
||||
"fileVersion": "2.3.2.0"
|
||||
},
|
||||
"lib/netstandard2.0/RedLockNet.SERedis.dll": {
|
||||
"assemblyVersion": "2.3.2.0",
|
||||
"fileVersion": "2.3.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {},
|
||||
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {},
|
||||
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {},
|
||||
@ -1637,6 +1663,7 @@
|
||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
||||
"Newtonsoft.Json": "13.0.4",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "8.0.3",
|
||||
"RedLock.net": "2.3.2",
|
||||
"StackExchange.Redis": "2.9.32",
|
||||
"Swashbuckle.AspNetCore": "6.6.2",
|
||||
"System.IdentityModel.Tokens.Jwt": "8.14.0"
|
||||
@ -1852,6 +1879,13 @@
|
||||
"path": "microsoft.aspnetcore.webutilities/2.3.0",
|
||||
"hashPath": "microsoft.aspnetcore.webutilities.2.3.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/1.1.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg==",
|
||||
"path": "microsoft.bcl.asyncinterfaces/1.1.0",
|
||||
"hashPath": "microsoft.bcl.asyncinterfaces.1.1.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.CodeCoverage/17.8.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
@ -2174,6 +2208,13 @@
|
||||
"path": "rabbitmq.client/7.1.2",
|
||||
"hashPath": "rabbitmq.client.7.1.2.nupkg.sha512"
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-jlrALAArm4dCE292U3EtRoMnVKJ9M6sunbZn/oG5OuzlGtTpusXBfvDrnGWbgGDlWV027f5E9H5CiVnPxiq8+g==",
|
||||
"path": "redlock.net/2.3.2",
|
||||
"hashPath": "redlock.net.2.3.2.nupkg.sha512"
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -18,6 +18,7 @@
|
||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
||||
"Newtonsoft.Json": "13.0.4",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "8.0.3",
|
||||
"RedLock.net": "2.3.2",
|
||||
"StackExchange.Redis": "2.9.32",
|
||||
"Swashbuckle.AspNetCore": "6.6.2",
|
||||
"System.IdentityModel.Tokens.Jwt": "8.14.0"
|
||||
@ -822,6 +823,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "6.0.0",
|
||||
"Microsoft.Extensions.Logging": "8.0.1",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
"StackExchange.Redis": "2.9.32"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/RedLockNet.Abstractions.dll": {
|
||||
"assemblyVersion": "2.3.2.0",
|
||||
"fileVersion": "2.3.2.0"
|
||||
},
|
||||
"lib/netstandard2.0/RedLockNet.SERedis.dll": {
|
||||
"assemblyVersion": "2.3.2.0",
|
||||
"fileVersion": "2.3.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"StackExchange.Redis/2.9.32": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
@ -1494,6 +1513,13 @@
|
||||
"path": "rabbitmq.client/7.1.2",
|
||||
"hashPath": "rabbitmq.client.7.1.2.nupkg.sha512"
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-jlrALAArm4dCE292U3EtRoMnVKJ9M6sunbZn/oG5OuzlGtTpusXBfvDrnGWbgGDlWV027f5E9H5CiVnPxiq8+g==",
|
||||
"path": "redlock.net/2.3.2",
|
||||
"hashPath": "redlock.net.2.3.2.nupkg.sha512"
|
||||
},
|
||||
"StackExchange.Redis/2.9.32": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+22038345c32db146ffb78173c5410f954daa64e0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+07bb01808f00b598a75ab9490d12e3514580fcab")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@ -1 +1 @@
|
||||
ee1fc45f192938903a153f1c2e3b53f60a2184cb806b87d9b57b487095b98264
|
||||
ed76e92b5e8795c53e98fa0f534a65801467f19a9f8a7b0c127cdc4e442cbb2a
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
97ad978fabe5fb8f7f258c9878659ee9a5f2492332ad5efd70b1d456ebc42a59
|
||||
f5bc8c7a1703bdc5e206f40b9687382b19faace8ff21b9d0a842dec47bff95a8
|
||||
|
||||
@ -144,3 +144,6 @@ C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\MassTransit.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\MassTransit.Abstractions.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\MassTransit.RabbitMqTransport.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\RabbitMQ.Client.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\Microsoft.Bcl.AsyncInterfaces.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\RedLockNet.Abstractions.dll
|
||||
C:\Users\nanxun\Documents\IM\backend\IMTest\bin\Debug\net8.0\RedLockNet.SERedis.dll
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -179,6 +179,10 @@
|
||||
"target": "Package",
|
||||
"version": "[8.0.21, )"
|
||||
},
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": {
|
||||
"target": "Package",
|
||||
"version": "[10.0.2, )"
|
||||
},
|
||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": {
|
||||
"target": "Package",
|
||||
"version": "[1.22.1, )"
|
||||
@ -191,6 +195,10 @@
|
||||
"target": "Package",
|
||||
"version": "[8.0.3, )"
|
||||
},
|
||||
"RedLock.net": {
|
||||
"target": "Package",
|
||||
"version": "[2.3.2, )"
|
||||
},
|
||||
"StackExchange.Redis": {
|
||||
"target": "Package",
|
||||
"version": "[2.9.32, )"
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)xunit.core\2.5.3\build\xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core\2.5.3\build\xunit.core.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage\17.8.0\build\netstandard2.0\Microsoft.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage\17.8.0\build\netstandard2.0\Microsoft.CodeCoverage.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk\17.8.0\build\netcoreapp3.1\Microsoft.NET.Test.Sdk.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk\17.8.0\build\netcoreapp3.1\Microsoft.NET.Test.Sdk.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)coverlet.collector\6.0.0\build\netstandard1.0\coverlet.collector.targets" Condition="Exists('$(NuGetPackageRoot)coverlet.collector\6.0.0\build\netstandard1.0\coverlet.collector.targets')" />
|
||||
|
||||
@ -491,6 +491,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/1.1.0": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"ref/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeCoverage/17.8.0": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
@ -599,10 +610,10 @@
|
||||
"buildMultiTargeting/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Abstractions/8.0.0": {
|
||||
"Microsoft.Extensions.Caching.Abstractions/10.0.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
"Microsoft.Extensions.Primitives": "10.0.2"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll": {
|
||||
@ -615,7 +626,7 @@
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
"buildTransitive/net8.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Memory/8.0.1": {
|
||||
@ -641,6 +652,25 @@
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis/10.0.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Caching.Abstractions": "10.0.2",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.2",
|
||||
"Microsoft.Extensions.Options": "10.0.2",
|
||||
"StackExchange.Redis": "2.7.27"
|
||||
},
|
||||
"compile": {
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.StackExchangeRedis.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.StackExchangeRedis.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
@ -679,7 +709,7 @@
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.2": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
|
||||
@ -692,7 +722,7 @@
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
"buildTransitive/net8.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.1": {
|
||||
@ -810,10 +840,11 @@
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
|
||||
"Microsoft.Extensions.Logging.Abstractions/10.0.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
|
||||
"System.Diagnostics.DiagnosticSource": "10.0.2"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
|
||||
@ -826,7 +857,7 @@
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets": {}
|
||||
"buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool/8.0.11": {
|
||||
@ -842,11 +873,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options/8.0.2": {
|
||||
"Microsoft.Extensions.Options/10.0.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
|
||||
"Microsoft.Extensions.Primitives": "10.0.2"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.Options.dll": {
|
||||
@ -859,10 +890,10 @@
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/Microsoft.Extensions.Options.targets": {}
|
||||
"buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives/8.0.0": {
|
||||
"Microsoft.Extensions.Primitives/10.0.2": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
|
||||
@ -875,7 +906,7 @@
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
"buildTransitive/net8.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Abstractions/8.14.0": {
|
||||
@ -1460,6 +1491,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "1.1.0",
|
||||
"Microsoft.Extensions.Logging": "2.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "2.0.0",
|
||||
"StackExchange.Redis": "2.0.513"
|
||||
},
|
||||
"compile": {
|
||||
"lib/netstandard2.0/RedLockNet.Abstractions.dll": {},
|
||||
"lib/netstandard2.0/RedLockNet.SERedis.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/RedLockNet.Abstractions.dll": {},
|
||||
"lib/netstandard2.0/RedLockNet.SERedis.dll": {}
|
||||
}
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {
|
||||
"type": "package",
|
||||
"runtimeTargets": {
|
||||
@ -1798,24 +1846,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource/4.3.0": {
|
||||
"System.Diagnostics.DiagnosticSource/10.0.2": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"System.Collections": "4.3.0",
|
||||
"System.Diagnostics.Tracing": "4.3.0",
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
},
|
||||
"compile": {
|
||||
"lib/netstandard1.3/_._": {
|
||||
"lib/net8.0/System.Diagnostics.DiagnosticSource.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": {
|
||||
"lib/net8.0/System.Diagnostics.DiagnosticSource.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net8.0/_._": {}
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.EventLog/6.0.0": {
|
||||
@ -2978,9 +3022,11 @@
|
||||
"MassTransit.RabbitMQ": "8.5.5",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.21",
|
||||
"Microsoft.AspNetCore.SignalR": "1.2.0",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "10.0.2",
|
||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
||||
"Newtonsoft.Json": "13.0.4",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "8.0.3",
|
||||
"RedLock.net": "2.3.2",
|
||||
"StackExchange.Redis": "2.9.32",
|
||||
"Swashbuckle.AspNetCore": "6.6.2",
|
||||
"System.IdentityModel.Tokens.Jwt": "8.14.0"
|
||||
@ -3442,6 +3488,30 @@
|
||||
"microsoft.aspnetcore.webutilities.nuspec"
|
||||
]
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/1.1.0": {
|
||||
"sha512": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg==",
|
||||
"type": "package",
|
||||
"path": "microsoft.bcl.asyncinterfaces/1.1.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE.TXT",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"lib/net461/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"lib/net461/Microsoft.Bcl.AsyncInterfaces.xml",
|
||||
"lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.xml",
|
||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.xml",
|
||||
"microsoft.bcl.asyncinterfaces.1.1.0.nupkg.sha512",
|
||||
"microsoft.bcl.asyncinterfaces.nuspec",
|
||||
"ref/net461/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"ref/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"ref/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"useSharedDesignerContext.txt",
|
||||
"version.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.CodeCoverage/17.8.0": {
|
||||
"sha512": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==",
|
||||
"type": "package",
|
||||
@ -3884,32 +3954,31 @@
|
||||
"tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Abstractions/8.0.0": {
|
||||
"sha512": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==",
|
||||
"Microsoft.Extensions.Caching.Abstractions/10.0.2": {
|
||||
"sha512": "WIRPDa/qoKHmJhTAPCO/zLu9kRLQ2Fd6HD5tzgdXJ3xGEVXDHP6FvakKJjynwKrVDld8H4G4tcbW53wuC/wxMQ==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.caching.abstractions/8.0.0",
|
||||
"path": "microsoft.extensions.caching.abstractions/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets",
|
||||
"buildTransitive/net462/_._",
|
||||
"buildTransitive/net6.0/_._",
|
||||
"buildTransitive/net8.0/_._",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets",
|
||||
"lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/net462/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net462/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml",
|
||||
"microsoft.extensions.caching.abstractions.8.0.0.nupkg.sha512",
|
||||
"microsoft.extensions.caching.abstractions.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.caching.abstractions.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
@ -3944,6 +4013,26 @@
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis/10.0.2": {
|
||||
"sha512": "WEx0VM6KVv4Bf6lwe4WQTd4EixIfw38ZU3u/7zMe+uC5fOyiANu8Os/qyiqv2iEsIJb296tbd2E2BTaWIha3Vg==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.caching.stackexchangeredis/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"lib/net10.0/Microsoft.Extensions.Caching.StackExchangeRedis.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.Caching.StackExchangeRedis.xml",
|
||||
"lib/net462/Microsoft.Extensions.Caching.StackExchangeRedis.dll",
|
||||
"lib/net462/Microsoft.Extensions.Caching.StackExchangeRedis.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.StackExchangeRedis.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Caching.StackExchangeRedis.xml",
|
||||
"microsoft.extensions.caching.stackexchangeredis.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.caching.stackexchangeredis.nuspec"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
|
||||
"sha512": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
|
||||
"type": "package",
|
||||
@ -4006,34 +4095,33 @@
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
|
||||
"sha512": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.2": {
|
||||
"sha512": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.2",
|
||||
"path": "microsoft.extensions.dependencyinjection.abstractions/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets",
|
||||
"buildTransitive/net462/_._",
|
||||
"buildTransitive/net6.0/_._",
|
||||
"buildTransitive/net8.0/_._",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets",
|
||||
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"microsoft.extensions.dependencyinjection.abstractions.8.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.dependencyinjection.abstractions.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.dependencyinjection.abstractions.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
@ -4197,15 +4285,14 @@
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
|
||||
"sha512": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==",
|
||||
"Microsoft.Extensions.Logging.Abstractions/10.0.2": {
|
||||
"sha512": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.logging.abstractions/8.0.2",
|
||||
"path": "microsoft.extensions.logging.abstractions/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll",
|
||||
@ -4252,20 +4339,20 @@
|
||||
"analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll",
|
||||
"buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"buildTransitive/net6.0/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets",
|
||||
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/net462/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net462/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml",
|
||||
"microsoft.extensions.logging.abstractions.8.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.logging.abstractions.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.logging.abstractions.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
@ -4289,15 +4376,14 @@
|
||||
"microsoft.extensions.objectpool.nuspec"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Options/8.0.2": {
|
||||
"sha512": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==",
|
||||
"Microsoft.Extensions.Options/10.0.2": {
|
||||
"sha512": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.options/8.0.2",
|
||||
"path": "microsoft.extensions.options/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll",
|
||||
@ -4316,52 +4402,51 @@
|
||||
"analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll",
|
||||
"buildTransitive/net461/Microsoft.Extensions.Options.targets",
|
||||
"buildTransitive/net462/Microsoft.Extensions.Options.targets",
|
||||
"buildTransitive/net6.0/Microsoft.Extensions.Options.targets",
|
||||
"buildTransitive/net8.0/Microsoft.Extensions.Options.targets",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets",
|
||||
"buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets",
|
||||
"lib/net10.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/net462/Microsoft.Extensions.Options.dll",
|
||||
"lib/net462/Microsoft.Extensions.Options.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/net9.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/net9.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Options.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Options.xml",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.Options.dll",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.Options.xml",
|
||||
"microsoft.extensions.options.8.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.options.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.options.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.Primitives/8.0.0": {
|
||||
"sha512": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==",
|
||||
"Microsoft.Extensions.Primitives/10.0.2": {
|
||||
"sha512": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.primitives/8.0.0",
|
||||
"path": "microsoft.extensions.primitives/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"buildTransitive/net461/Microsoft.Extensions.Primitives.targets",
|
||||
"buildTransitive/net462/_._",
|
||||
"buildTransitive/net6.0/_._",
|
||||
"buildTransitive/net8.0/_._",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets",
|
||||
"lib/net10.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net10.0/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/net462/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net462/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/net9.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/net9.0/Microsoft.Extensions.Primitives.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Primitives.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.Primitives.xml",
|
||||
"microsoft.extensions.primitives.8.0.0.nupkg.sha512",
|
||||
"microsoft.extensions.primitives.10.0.2.nupkg.sha512",
|
||||
"microsoft.extensions.primitives.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
@ -5036,6 +5121,24 @@
|
||||
"rabbitmq.client.nuspec"
|
||||
]
|
||||
},
|
||||
"RedLock.net/2.3.2": {
|
||||
"sha512": "jlrALAArm4dCE292U3EtRoMnVKJ9M6sunbZn/oG5OuzlGtTpusXBfvDrnGWbgGDlWV027f5E9H5CiVnPxiq8+g==",
|
||||
"type": "package",
|
||||
"path": "redlock.net/2.3.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"lib/net461/RedLockNet.Abstractions.dll",
|
||||
"lib/net461/RedLockNet.SERedis.dll",
|
||||
"lib/net472/RedLockNet.Abstractions.dll",
|
||||
"lib/net472/RedLockNet.SERedis.dll",
|
||||
"lib/netstandard2.0/RedLockNet.Abstractions.dll",
|
||||
"lib/netstandard2.0/RedLockNet.SERedis.dll",
|
||||
"redlock-icon.png",
|
||||
"redlock.net.2.3.2.nupkg.sha512",
|
||||
"redlock.net.nuspec"
|
||||
]
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {
|
||||
"sha512": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==",
|
||||
"type": "package",
|
||||
@ -5703,25 +5806,32 @@
|
||||
"system.diagnostics.debug.nuspec"
|
||||
]
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource/4.3.0": {
|
||||
"sha512": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
|
||||
"System.Diagnostics.DiagnosticSource/10.0.2": {
|
||||
"sha512": "lYWBy8fKkJHaRcOuw30d67PrtVjR5754sz5Wl76s8P+vJ9FSThh9b7LIcTSODx1LY7NB3Srvg+JMnzd67qNZOw==",
|
||||
"type": "package",
|
||||
"path": "system.diagnostics.diagnosticsource/4.3.0",
|
||||
"path": "system.diagnostics.diagnosticsource/10.0.2",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"ThirdPartyNotices.txt",
|
||||
"dotnet_library_license.txt",
|
||||
"lib/net46/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/net46/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/netstandard1.1/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/netstandard1.1/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/netstandard1.3/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/portable-net45+win8+wpa81/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/portable-net45+win8+wpa81/System.Diagnostics.DiagnosticSource.xml",
|
||||
"system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512",
|
||||
"system.diagnostics.diagnosticsource.nuspec"
|
||||
"Icon.png",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"buildTransitive/net461/System.Diagnostics.DiagnosticSource.targets",
|
||||
"buildTransitive/net462/_._",
|
||||
"buildTransitive/net8.0/_._",
|
||||
"buildTransitive/netcoreapp2.0/System.Diagnostics.DiagnosticSource.targets",
|
||||
"lib/net10.0/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/net10.0/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/net462/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/net462/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/net8.0/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/net8.0/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/net9.0/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/net9.0/System.Diagnostics.DiagnosticSource.xml",
|
||||
"lib/netstandard2.0/System.Diagnostics.DiagnosticSource.dll",
|
||||
"lib/netstandard2.0/System.Diagnostics.DiagnosticSource.xml",
|
||||
"system.diagnostics.diagnosticsource.10.0.2.nupkg.sha512",
|
||||
"system.diagnostics.diagnosticsource.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"System.Diagnostics.EventLog/6.0.0": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "tVGTA3KwBHQ=",
|
||||
"dgSpecHash": "ueA0djhC8vQ=",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\IMTest.csproj",
|
||||
"expectedPackageFiles": [
|
||||
@ -32,6 +32,7 @@
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.signalr.protocols.json\\1.2.0\\microsoft.aspnetcore.signalr.protocols.json.1.2.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.websockets\\2.3.0\\microsoft.aspnetcore.websockets.2.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.webutilities\\2.3.0\\microsoft.aspnetcore.webutilities.2.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.bcl.asyncinterfaces\\1.1.0\\microsoft.bcl.asyncinterfaces.1.1.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.codecoverage\\17.8.0\\microsoft.codecoverage.17.8.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.csharp\\4.7.0\\microsoft.csharp.4.7.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.entityframeworkcore\\8.0.22\\microsoft.entityframeworkcore.8.0.22.nupkg.sha512",
|
||||
@ -40,21 +41,22 @@
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.entityframeworkcore.inmemory\\8.0.22\\microsoft.entityframeworkcore.inmemory.8.0.22.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\8.0.13\\microsoft.entityframeworkcore.relational.8.0.13.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.apidescription.server\\6.0.5\\microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\8.0.0\\microsoft.extensions.caching.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\10.0.2\\microsoft.extensions.caching.abstractions.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.caching.memory\\8.0.1\\microsoft.extensions.caching.memory.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.caching.stackexchangeredis\\10.0.2\\microsoft.extensions.caching.stackexchangeredis.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\8.0.0\\microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\8.0.1\\microsoft.extensions.dependencyinjection.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\8.0.2\\microsoft.extensions.dependencyinjection.abstractions.8.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\10.0.2\\microsoft.extensions.dependencyinjection.abstractions.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.diagnostics.abstractions\\8.0.1\\microsoft.extensions.diagnostics.abstractions.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.diagnostics.healthchecks\\8.0.0\\microsoft.extensions.diagnostics.healthchecks.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.diagnostics.healthchecks.abstractions\\8.0.0\\microsoft.extensions.diagnostics.healthchecks.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.fileproviders.abstractions\\8.0.0\\microsoft.extensions.fileproviders.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.hosting.abstractions\\8.0.1\\microsoft.extensions.hosting.abstractions.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.logging\\8.0.1\\microsoft.extensions.logging.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\8.0.2\\microsoft.extensions.logging.abstractions.8.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\10.0.2\\microsoft.extensions.logging.abstractions.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.objectpool\\8.0.11\\microsoft.extensions.objectpool.8.0.11.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.options\\8.0.2\\microsoft.extensions.options.8.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.primitives\\8.0.0\\microsoft.extensions.primitives.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.options\\10.0.2\\microsoft.extensions.options.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.extensions.primitives\\10.0.2\\microsoft.extensions.primitives.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.14.0\\microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.14.0\\microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.identitymodel.logging\\8.14.0\\microsoft.identitymodel.logging.8.14.0.nupkg.sha512",
|
||||
@ -78,6 +80,7 @@
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\pipelines.sockets.unofficial\\2.2.8\\pipelines.sockets.unofficial.2.2.8.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\pomelo.entityframeworkcore.mysql\\8.0.3\\pomelo.entityframeworkcore.mysql.8.0.3.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\rabbitmq.client\\7.1.2\\rabbitmq.client.7.1.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\redlock.net\\2.3.2\\redlock.net.2.3.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl\\4.3.0\\runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl\\4.3.0\\runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl\\4.3.0\\runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512",
|
||||
@ -105,7 +108,7 @@
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.collections.concurrent\\4.3.0\\system.collections.concurrent.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.console\\4.3.0\\system.console.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.debug\\4.3.0\\system.diagnostics.debug.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.diagnosticsource\\4.3.0\\system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.diagnosticsource\\10.0.2\\system.diagnostics.diagnosticsource.10.0.2.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.eventlog\\6.0.0\\system.diagnostics.eventlog.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.tools\\4.3.0\\system.diagnostics.tools.4.3.0.nupkg.sha512",
|
||||
"C:\\Users\\nanxun\\.nuget\\packages\\system.diagnostics.tracing\\4.3.0\\system.diagnostics.tracing.4.3.0.nupkg.sha512",
|
||||
|
||||
@ -26,10 +26,10 @@ namespace IM_API.Application.EventHandlers.FriendAddHandler
|
||||
{
|
||||
ChatType = ChatType.PRIVATE,
|
||||
Content = "您有新的好友关系已添加",
|
||||
MsgId = @event.EventId.ToString(),
|
||||
//MsgId = @event.EventId.ToString(),
|
||||
ReceiverId = @event.ResponseUserId,
|
||||
SenderId = @event.RequestUserId,
|
||||
TimeStamp = DateTime.UtcNow
|
||||
TimeStamp = DateTime.Now
|
||||
});
|
||||
await _chathub.Clients.Users(usersList).SendAsync("ReceiveMessage", res);
|
||||
}
|
||||
|
||||
@ -35,28 +35,15 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler
|
||||
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
|
||||
if (@event.ChatType == ChatType.PRIVATE)
|
||||
await _conversationService.UpdateConversationAfterSentAsync(new Dtos.Conversation.UpdateConversationDto
|
||||
{
|
||||
Conversation? userAConversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.UserId == @event.MsgSenderId && x.TargetId == @event.MsgRecipientId
|
||||
);
|
||||
Conversation? userBConversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.UserId == @event.MsgRecipientId && x.TargetId == @event.MsgSenderId
|
||||
);
|
||||
if (userAConversation is null || userBConversation is null)
|
||||
{
|
||||
_logger.LogError("消息事件更新会话信息失败:{@event}", @event);
|
||||
}
|
||||
userAConversation.LastMessage = @event.MessageContent;
|
||||
userAConversation.LastReadMessageId = @event.MessageId;
|
||||
userAConversation.LastMessageTime = @event.MessageCreated;
|
||||
userBConversation.LastMessage = @event.MessageContent;
|
||||
userBConversation.UnreadCount += 1;
|
||||
userBConversation.LastMessageTime = @event.MessageCreated;
|
||||
_context.UpdateRange(userAConversation, userBConversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
LastMessage = @event.MessageContent,
|
||||
LastSequenceId = @event.SequenceId,
|
||||
ReceiptId = @event.MsgRecipientId,
|
||||
SenderId = @event.MsgSenderId,
|
||||
StreamKey = @event.StreamKey,
|
||||
DateTime = @event.MessageCreated
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
using IM_API.Domain.Events;
|
||||
using MassTransit;
|
||||
using IM_API.Interface.Services;
|
||||
using AutoMapper;
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.MessageCreatedHandler
|
||||
{
|
||||
public class MessageCreatedDbHandler : IConsumer<MessageCreatedEvent>
|
||||
{
|
||||
private readonly IMessageSevice _messageService;
|
||||
public readonly IMapper _mapper;
|
||||
public MessageCreatedDbHandler(IMessageSevice messageSevice, IMapper mapper)
|
||||
{
|
||||
_messageService = messageSevice;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
var msg = _mapper.Map<Message>(@event);
|
||||
await _messageService.MakeMessageAsync(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ using IM_API.Dtos;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
@ -22,21 +23,31 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler
|
||||
|
||||
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var @event = context.Message;
|
||||
if (@event.ChatType == Models.ChatType.PRIVATE)
|
||||
Console.WriteLine($"[SignalR]handlerCreated!");
|
||||
try
|
||||
{
|
||||
MessageBaseDto messageBaseDto = new MessageBaseDto
|
||||
{
|
||||
MsgId = @event.MessageId.ToString(),
|
||||
ChatType = @event.ChatType,
|
||||
Content = @event.MessageContent,
|
||||
GroupMemberId = null,
|
||||
ReceiverId = @event.MsgRecipientId,
|
||||
SenderId = @event.MsgSenderId,
|
||||
TimeStamp = @event.MessageCreated,
|
||||
Type = @event.MessageMsgType
|
||||
};
|
||||
await _hub.Clients.Users(@event.MsgRecipientId.ToString()).SendAsync("ReceiveMessage", messageBaseDto);
|
||||
// 先转成实体,如果这一步都报错,说明之前的 sbyte -> enum 还没改好
|
||||
var entity = _mapper.Map<Message>(@event);
|
||||
|
||||
// 再从实体转 VO,这是最稳妥的路径,因为这两者的映射你肯定配过了
|
||||
var messageBaseVo = _mapper.Map<MessageBaseVo>(entity);
|
||||
|
||||
// 2. 打印日志确认逻辑执行到这里了
|
||||
Console.WriteLine($"[SignalR] 准备向所有人广播消息: {messageBaseVo.Content}");
|
||||
|
||||
// 3. 执行广播
|
||||
await _hub.Clients.User(@event.MsgRecipientId.ToString()).SendAsync("ReceiveMessage", new HubResponse<MessageBaseVo>("Event", messageBaseVo));
|
||||
|
||||
Console.WriteLine("[SignalR] 广播指令已发出");
|
||||
Console.ResetColor();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SignalR] 发送失败: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
throw; // 抛出异常触发 MassTransit 重试
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
using IM_API.Application.EventHandlers.FriendAddHandler;
|
||||
using AutoMapper;
|
||||
using IM_API.Application.EventHandlers.FriendAddHandler;
|
||||
using IM_API.Application.EventHandlers.MessageCreatedHandler;
|
||||
using IM_API.Application.EventHandlers.RequestFriendHandler;
|
||||
using IM_API.Configs.Options;
|
||||
using MassTransit;
|
||||
using MySqlConnector;
|
||||
|
||||
namespace IM_API.Configs
|
||||
{
|
||||
public static class MQConfig
|
||||
{
|
||||
public static IServiceCollection AddRabbitMQ(this IServiceCollection services, RabbitMqOptions options)
|
||||
public static IServiceCollection AddRabbitMQ(this IServiceCollection services, RabbitMQOptions options)
|
||||
{
|
||||
services.AddMassTransit(x =>
|
||||
{
|
||||
@ -17,7 +20,7 @@ namespace IM_API.Configs
|
||||
x.AddConsumer<FriendAddSignalRHandler>();
|
||||
x.AddConsumer<RequestFriendSignalRHandler>();
|
||||
x.AddConsumer<FriendAddConversationHandler>();
|
||||
|
||||
x.AddConsumer<MessageCreatedDbHandler>();
|
||||
x.UsingRabbitMq((ctx,cfg) =>
|
||||
{
|
||||
cfg.Host(options.Host, "/", h =>
|
||||
@ -26,7 +29,15 @@ namespace IM_API.Configs
|
||||
h.Password(options.Password);
|
||||
});
|
||||
cfg.ConfigureEndpoints(ctx);
|
||||
});
|
||||
cfg.UseMessageRetry(r =>
|
||||
{
|
||||
r.Handle<IOException>();
|
||||
r.Handle<MySqlException>();
|
||||
r.Ignore<AutoMapperMappingException>();
|
||||
r.Exponential(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(2));
|
||||
});
|
||||
cfg.ConfigureEndpoints(ctx);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -34,11 +45,5 @@ namespace IM_API.Configs
|
||||
}
|
||||
}
|
||||
|
||||
public class RabbitMqOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get;set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Auth;
|
||||
using IM_API.Dtos.Conversation;
|
||||
using IM_API.Dtos.Friend;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Conversation;
|
||||
using IM_API.VOs.Message;
|
||||
|
||||
namespace IM_API.Configs
|
||||
{
|
||||
@ -41,13 +43,13 @@ namespace IM_API.Configs
|
||||
.ForMember(dest => dest.FriendId , opt => opt.MapFrom(src => src.ToUserId))
|
||||
.ForMember(dest => dest.StatusEnum , opt =>opt.MapFrom(src => FriendStatus.Pending))
|
||||
.ForMember(dest => dest.RemarkName , opt => opt.MapFrom(src => src.RemarkName))
|
||||
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.Now))
|
||||
;
|
||||
//发起好友请求转换请求对象
|
||||
CreateMap<FriendRequestDto, FriendRequest>()
|
||||
.ForMember(dest => dest.RequestUser , opt => opt.MapFrom(src => src.FromUserId))
|
||||
.ForMember(dest => dest.ResponseUser , opt => opt.MapFrom(src => src.ToUserId))
|
||||
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.Now))
|
||||
.ForMember(dest => dest.StateEnum , opt => opt.MapFrom(src => FriendRequestState.Pending))
|
||||
.ForMember(dest => dest.Description , opt => opt.MapFrom(src => src.Description))
|
||||
;
|
||||
@ -59,15 +61,15 @@ namespace IM_API.Configs
|
||||
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
|
||||
;
|
||||
//消息模型转换
|
||||
CreateMap<Message, MessageBaseDto>()
|
||||
.ForMember(dest => dest.Type , opt => opt.MapFrom(src => src.MsgTypeEnum.ToString()))
|
||||
.ForMember(dest => dest.MsgId , opt => opt.MapFrom(src => src.Id))
|
||||
CreateMap<Message, MessageBaseVo>()
|
||||
.ForMember(dest => dest.Type , opt => opt.MapFrom(src => src.MsgTypeEnum))
|
||||
.ForMember(dest => dest.MsgId , opt => opt.MapFrom(src => src.ClientMsgId))
|
||||
.ForMember(dest => dest.SenderId , opt => opt.MapFrom(src => src.Sender))
|
||||
.ForMember(dest => dest.ChatType , opt => opt.MapFrom(src => src.ChatTypeEnum.ToString()))
|
||||
.ForMember(dest => dest.ChatType , opt => opt.MapFrom(src => src.ChatTypeEnum))
|
||||
.ForMember(dest => dest.ReceiverId, opt => opt.MapFrom(src => src.Recipient))
|
||||
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
|
||||
.ForMember(dest => dest.TimeStamp, opt => opt.MapFrom(src => src.Created))
|
||||
.ForMember(dest => dest.GroupMemberId , opt => opt.MapFrom(src => src.GroupMemberId))
|
||||
.ForMember(dest => dest.SequenceId, opt => opt.MapFrom(src => src.SequenceId))
|
||||
;
|
||||
CreateMap<MessageBaseDto, Message>()
|
||||
.ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.SenderId))
|
||||
@ -78,9 +80,9 @@ namespace IM_API.Configs
|
||||
.ForMember(dest => dest.Recipient, opt => opt.MapFrom(src => src.ReceiverId))
|
||||
.ForMember(dest => dest.StreamKey, opt => opt.Ignore() )
|
||||
.ForMember(dest => dest.StateEnum, opt => opt.MapFrom(src => MessageState.Sent))
|
||||
.ForMember(dest => dest.GroupMemberId, opt => opt.MapFrom(src => src.GroupMemberId))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.MsgType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ClientMsgId, opt => opt.MapFrom(src => src.MsgId))
|
||||
;
|
||||
|
||||
//会话对象深拷贝
|
||||
@ -99,41 +101,58 @@ namespace IM_API.Configs
|
||||
.ForMember(dest => dest.MessageContent, opt => opt.MapFrom(src => src.Content))
|
||||
.ForMember(dest => dest.State, opt => opt.MapFrom(src => src.StateEnum))
|
||||
.ForMember(dest => dest.MessageCreated, opt => opt.MapFrom(src => src.Created))
|
||||
.ForMember(dest => dest.MessageId, opt => opt.MapFrom(src => src.Id))
|
||||
.ForMember(dest => dest.MsgRecipientId, opt => opt.MapFrom(src => src.Recipient))
|
||||
.ForMember(dest => dest.MsgSenderId, opt => opt.MapFrom(src => src.Sender))
|
||||
.ForMember(dest => dest.EventId, opt => opt.MapFrom(src => Guid.NewGuid()))
|
||||
.ForMember(dest => dest.AggregateId, opt => opt.MapFrom(src => src.Sender.ToString()))
|
||||
.ForMember(dest => dest.OccurredAt , opt => opt.MapFrom(src => DateTime.Now))
|
||||
.ForMember(dest => dest.OperatorId, opt => opt.MapFrom(src => src.Sender))
|
||||
.ForMember(dest => dest.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
|
||||
;
|
||||
|
||||
CreateMap<MessageCreatedEvent, Message>()
|
||||
.ForMember(dest => dest.SequenceId, opt => opt.MapFrom(src => src.SequenceId))
|
||||
.ForMember(dest => dest.ClientMsgId, opt => opt.MapFrom(src => src.ClientMsgId))
|
||||
.ForMember(dest => dest.StateEnum, opt => opt.MapFrom(src => src.State))
|
||||
.ForMember(dest => dest.ChatTypeEnum, opt => opt.MapFrom(src => src.ChatType))
|
||||
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.MessageContent))
|
||||
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.MessageCreated))
|
||||
.ForMember(dest => dest.MsgTypeEnum, opt => opt.MapFrom(src => src.MessageMsgType))
|
||||
.ForMember(dest => dest.Recipient, opt => opt.MapFrom(src => src.MsgRecipientId))
|
||||
.ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.MsgSenderId))
|
||||
.ForMember(dest => dest.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.State, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.MsgType, opt => opt.Ignore());
|
||||
|
||||
//消息发送事件转换会话对象
|
||||
CreateMap<MessageCreatedEvent, Conversation>()
|
||||
.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.MessageId))
|
||||
//.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.MessageId))
|
||||
.ForMember(dest => dest.LastMessage, opt => opt.MapFrom(src => src.MessageContent))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => (int)src.ChatType))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatType))
|
||||
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.MsgSenderId))
|
||||
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.MsgRecipientId))
|
||||
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => 0))
|
||||
.ForMember(dest => dest.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
|
||||
.ForMember(dest => dest.LastMessageTime, opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||
.ForMember(dest => dest.LastMessageTime, opt => opt.MapFrom(src => DateTime.Now))
|
||||
;
|
||||
|
||||
//创建会话对象
|
||||
CreateMap<Conversation, ConversationDto>()
|
||||
CreateMap<Conversation, ConversationVo>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
|
||||
.ForMember(dest => dest.LastMessage, opt => opt.MapFrom(src => src.LastMessage))
|
||||
.ForMember(dest => dest.LastReadMessage, opt => opt.MapFrom(src => src.LastReadMessage))
|
||||
.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.LastReadMessageId))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatType))
|
||||
.ForMember(dest => dest.LastSequenceId, opt => opt.MapFrom(src => src.LastReadSequenceId))
|
||||
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatTypeEnum))
|
||||
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.LastMessageTime))
|
||||
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.TargetId))
|
||||
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => src.UnreadCount))
|
||||
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId));
|
||||
|
||||
CreateMap<Friend, ConversationDto>()
|
||||
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
|
||||
CreateMap<Friend, ConversationVo>()
|
||||
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.FriendNavigation.Avatar))
|
||||
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.RemarkName));
|
||||
|
||||
CreateMap<Group, ConversationDto>()
|
||||
CreateMap<Group, ConversationVo>()
|
||||
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
|
||||
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
|
||||
|
||||
@ -147,10 +166,11 @@ namespace IM_API.Configs
|
||||
CreateMap<GroupCreateDto, Group>()
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
|
||||
.ForMember(dest => dest.Avatar, opt => opt.MapFrom(src => src.Avatar))
|
||||
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => DateTime.Now))
|
||||
.ForMember(dest => dest.AllMembersBannedEnum, opt => opt.MapFrom(src => GroupAllMembersBanned.ALLOWED))
|
||||
.ForMember(dest => dest.AuhorityEnum, opt => opt.MapFrom(src => GroupAuhority.REQUIRE_CONSENT))
|
||||
.ForMember(dest => dest.StatusEnum, opt => opt.MapFrom(src => GroupStatus.Normal));
|
||||
.ForMember(dest => dest.StatusEnum, opt => opt.MapFrom(src => GroupStatus.Normal))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
backend/IM_API/Configs/Options/ConnectionOptions.cs
Normal file
8
backend/IM_API/Configs/Options/ConnectionOptions.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace IM_API.Configs.Options
|
||||
{
|
||||
public class ConnectionOptions
|
||||
{
|
||||
public string DefaultConnection { get; set; }
|
||||
public string Redis { get; set; }
|
||||
}
|
||||
}
|
||||
10
backend/IM_API/Configs/Options/RabbitMQOptions.cs
Normal file
10
backend/IM_API/Configs/Options/RabbitMQOptions.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace IM_API.Configs.Options
|
||||
{
|
||||
public class RabbitMQOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,10 @@ using IM_API.Services;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RedLockNet;
|
||||
using RedLockNet.SERedis;
|
||||
using RedLockNet.SERedis.Configuration;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Configs
|
||||
{
|
||||
@ -17,14 +21,23 @@ namespace IM_API.Configs
|
||||
{
|
||||
|
||||
services.AddAutoMapper(typeof(MapperConfig));
|
||||
services.AddTransient<IAuthService, AuthService>();
|
||||
services.AddTransient<IUserService, UserService>();
|
||||
services.AddTransient<IFriendSerivce, FriendService>();
|
||||
services.AddTransient<IMessageSevice, MessageService>();
|
||||
services.AddTransient<IConversationService, ConversationService>();
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
services.AddScoped<IFriendSerivce, FriendService>();
|
||||
services.AddScoped<IMessageSevice, MessageService>();
|
||||
services.AddScoped<IConversationService, ConversationService>();
|
||||
services.AddScoped<IGroupService, GroupService>();
|
||||
services.AddScoped<ISequenceIdService, SequenceIdService>();
|
||||
services.AddScoped<ICacheService, RedisCacheService>();
|
||||
services.AddScoped<IEventBus, InMemoryEventBus>();
|
||||
services.AddSingleton<IJWTService, JWTService>();
|
||||
services.AddSingleton<IRefreshTokenService, RedisRefreshTokenService>();
|
||||
services.AddSingleton<IDistributedLockFactory>(sp =>
|
||||
{
|
||||
var connection = sp.GetRequiredService<IConnectionMultiplexer>();
|
||||
// 这里可以配置多个 Redis 节点提高安全性,单机运行传一个即可
|
||||
return RedLockFactory.Create(new List<RedLockMultiplexer> { new RedLockMultiplexer(connection) });
|
||||
});
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddModelValidation(this IServiceCollection services, IConfiguration configuration)
|
||||
@ -40,7 +53,7 @@ namespace IM_API.Configs
|
||||
Field = e.Key,
|
||||
Message = e.Value.Errors.First().ErrorMessage
|
||||
});
|
||||
|
||||
Console.WriteLine(errors);
|
||||
return new BadRequestObjectResult(new BaseResponse<object?>(CodeDefine.PARAMETER_ERROR.Code, errors.First().Message));
|
||||
};
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ using IM_API.Dtos.Auth;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Auth;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -44,7 +45,7 @@ namespace IM_API.Controllers
|
||||
(string token,DateTime expiresAt) = _jwtService.CreateAccessTokenForUser(user.Id,user.Username,"user");
|
||||
//生成刷新凭证
|
||||
string refreshToken = await _refreshTokenService.CreateRefreshTokenAsync(user.Id);
|
||||
var res = new BaseResponse<LoginDto>(new LoginDto(userInfo,token,refreshToken, expiresAt));
|
||||
var res = new BaseResponse<LoginVo>(new LoginVo(userInfo,token,refreshToken, expiresAt));
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpPost]
|
||||
@ -55,18 +56,18 @@ namespace IM_API.Controllers
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<LoginDto>),StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(BaseResponse<LoginVo>),StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Refresh(RefreshDto dto)
|
||||
{
|
||||
(bool ok,int userId) = await _refreshTokenService.ValidateRefreshTokenAsync(dto.refreshToken);
|
||||
if (!ok)
|
||||
{
|
||||
var err = new BaseResponse<LoginDto>(CodeDefine.AUTH_FAILED);
|
||||
var err = new BaseResponse<LoginVo>(CodeDefine.AUTH_FAILED);
|
||||
return Unauthorized(err);
|
||||
}
|
||||
var userinfo = await _userService.GetUserInfoAsync(userId);
|
||||
(string token,DateTime expiresAt) = _jwtService.CreateAccessTokenForUser(userinfo.Id,userinfo.Username,"user");
|
||||
var res = new BaseResponse<LoginDto>(new LoginDto(userinfo,token, dto.refreshToken, expiresAt));
|
||||
var res = new BaseResponse<LoginVo>(new LoginVo(userinfo,token, dto.refreshToken, expiresAt));
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Conversation;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.VOs.Conversation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -26,7 +26,7 @@ namespace IM_API.Controllers
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var list = await _conversationSerivice.GetConversationsAsync(int.Parse(userIdStr));
|
||||
var res = new BaseResponse<List<ConversationDto>>(list);
|
||||
var res = new BaseResponse<List<ConversationVo>>(list);
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpGet]
|
||||
@ -34,7 +34,7 @@ namespace IM_API.Controllers
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var conversation = await _conversationSerivice.GetConversationByIdAsync(int.Parse(userIdStr), conversationId);
|
||||
var res = new BaseResponse<ConversationDto>(conversation);
|
||||
var res = new BaseResponse<ConversationVo>(conversation);
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpPost]
|
||||
|
||||
43
backend/IM_API/Controllers/GroupController.cs
Normal file
43
backend/IM_API/Controllers/GroupController.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Interface.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace IM_API.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class GroupController : ControllerBase
|
||||
{
|
||||
private readonly IGroupService _groupService;
|
||||
private readonly ILogger<GroupController> _logger;
|
||||
public GroupController(IGroupService groupService, ILogger<GroupController> logger)
|
||||
{
|
||||
_groupService = groupService;
|
||||
_logger = logger;
|
||||
}
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(BaseResponse<List<GroupInfoDto>>) ,StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetGroups(int page = 1, int limit = 100, bool desc = false)
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var list = await _groupService.GetGroupListAsync(int.Parse(userIdStr), page, limit, desc);
|
||||
var res = new BaseResponse<List<GroupInfoDto>>(list);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<GroupInfoDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> CreateGroup([FromBody]GroupCreateDto groupCreateDto)
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var groupInfo = await _groupService.CreateGroupAsync(int.Parse(userIdStr), groupCreateDto);
|
||||
var res = new BaseResponse<GroupInfoDto>(groupInfo);
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Application.Interfaces;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Message;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.VOs.Message;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -15,31 +19,36 @@ namespace IM_API.Controllers
|
||||
{
|
||||
private readonly IMessageSevice _messageService;
|
||||
private readonly ILogger<MessageController> _logger;
|
||||
public MessageController(IMessageSevice messageService, ILogger<MessageController> logger)
|
||||
private readonly IEventBus _eventBus;
|
||||
public MessageController(IMessageSevice messageService, ILogger<MessageController> logger, IEventBus eventBus)
|
||||
{
|
||||
_messageService = messageService;
|
||||
_logger = logger;
|
||||
_eventBus = eventBus;
|
||||
}
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendPrivateMessage(MessageBaseDto dto)
|
||||
[ProducesResponseType(typeof(BaseResponse<MessageBaseVo>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SendMessage(MessageBaseDto dto)
|
||||
{
|
||||
var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
await _messageService.SendPrivateMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
|
||||
return Ok(new BaseResponse<object?>());
|
||||
}
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendGroupMessage(MessageBaseDto dto)
|
||||
{
|
||||
var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
|
||||
return Ok(new BaseResponse<object?>());
|
||||
MessageBaseVo messageBaseVo = new MessageBaseVo();
|
||||
if(dto.ChatType == Models.ChatType.PRIVATE)
|
||||
{
|
||||
messageBaseVo = await _messageService.SendPrivateMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBaseVo = await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
|
||||
}
|
||||
return Ok(new BaseResponse<MessageBaseVo>(messageBaseVo));
|
||||
}
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetMessageList([Required]int conversationId, int? msgId, int? pageSize)
|
||||
[ProducesResponseType(typeof(BaseResponse<List<MessageBaseVo>>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetMessageList([FromQuery]MessageQueryDto dto)
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr), conversationId, msgId, pageSize, false);
|
||||
var res = new BaseResponse<List<MessageBaseDto>>(msgList);
|
||||
var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr),dto);
|
||||
var res = new BaseResponse<List<MessageBaseVo>>(msgList);
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ namespace IM_API.Domain.Events
|
||||
public abstract record DomainEvent: IEvent
|
||||
{
|
||||
public Guid EventId { get; init; } = Guid.NewGuid();
|
||||
public DateTime OccurredAt { get; init; } = DateTime.UtcNow;
|
||||
public DateTimeOffset OccurredAt { get; init; } = DateTime.Now;
|
||||
public long OperatorId { get; init; }
|
||||
public string AggregateId { get; init; } = "";
|
||||
public abstract string EventType { get; }
|
||||
|
||||
@ -19,7 +19,7 @@ namespace IM_API.Domain.Events
|
||||
/// <summary>
|
||||
/// 好友关系创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; init; }
|
||||
public DateTimeOffset Created { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,13 +8,14 @@ namespace IM_API.Domain.Events
|
||||
public override string EventType => "IM.MESSAGE.MESSAGE_CREATED";
|
||||
public ChatType ChatType { get; set; }
|
||||
public MessageMsgType MessageMsgType { get; set; }
|
||||
public int MessageId { get; set; }
|
||||
public long SequenceId { get; set; }
|
||||
public string MessageContent { get; set; }
|
||||
public int MsgSenderId { get; set; }
|
||||
public int MsgRecipientId { get; set; }
|
||||
public MessageState State { get; set; }
|
||||
public DateTime MessageCreated { get; set; }
|
||||
public DateTimeOffset MessageCreated { get; set; }
|
||||
public string StreamKey { get; set; }
|
||||
public Guid ClientMsgId { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
12
backend/IM_API/Dtos/Conversation/UpdateConversationDto.cs
Normal file
12
backend/IM_API/Dtos/Conversation/UpdateConversationDto.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace IM_API.Dtos.Conversation
|
||||
{
|
||||
public class UpdateConversationDto
|
||||
{
|
||||
public string StreamKey { get; set; }
|
||||
public int SenderId { get; set; }
|
||||
public int ReceiptId { get; set; }
|
||||
public string LastMessage { get; set; }
|
||||
public long LastSequenceId { get; set; }
|
||||
public DateTimeOffset DateTime { get; set; }
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ namespace IM_API.Dtos.Friend
|
||||
|
||||
public FriendStatus StatusEnum { get; init; }
|
||||
|
||||
public DateTime Created { get; init; }
|
||||
public DateTimeOffset Created { get; init; }
|
||||
|
||||
public string RemarkName { get; init; } = string.Empty;
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ namespace IM_API.Dtos.Friend
|
||||
/// <summary>
|
||||
/// 申请时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请附言
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using IM_API.Models;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace IM_API.Dtos.Group
|
||||
{
|
||||
@ -7,11 +8,15 @@ namespace IM_API.Dtos.Group
|
||||
/// <summary>
|
||||
/// 群聊名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "群名称必填")]
|
||||
[MaxLength(20, ErrorMessage = "群名称不能大于20字符")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 群头像
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "群头像必填")]
|
||||
public string Avatar { get; set; } = null!;
|
||||
public List<int>? UserIDs { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ namespace IM_API.Dtos.Group
|
||||
/// <summary>
|
||||
/// 群聊创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 群头像
|
||||
|
||||
@ -14,6 +14,7 @@ namespace IM_API.Dtos
|
||||
Code = CodeDefine.SUCCESS.Code;
|
||||
Message = CodeDefine.SUCCESS.Message;
|
||||
Type = HubResponseType.ActionStatus;
|
||||
Method = method;
|
||||
}
|
||||
public HubResponse(string method,T data)
|
||||
{
|
||||
@ -21,6 +22,7 @@ namespace IM_API.Dtos
|
||||
Message = CodeDefine.SUCCESS.Message;
|
||||
Type = HubResponseType.ActionStatus;
|
||||
Data = data;
|
||||
Method = method;
|
||||
}
|
||||
public HubResponse(CodeDefine codedefine,string method)
|
||||
{
|
||||
|
||||
18
backend/IM_API/Dtos/Message/MessageQueryDto.cs
Normal file
18
backend/IM_API/Dtos/Message/MessageQueryDto.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace IM_API.Dtos.Message
|
||||
{
|
||||
public class MessageQueryDto
|
||||
{
|
||||
[Required(ErrorMessage = "会话ID必填")]
|
||||
public int ConversationId { get; set; }
|
||||
|
||||
// 锚点序号(如果为空,说明是第一次进聊天框,拉最新的)
|
||||
public long? Cursor { get; set; }
|
||||
|
||||
// 查询方向:0 - 查旧(Before), 1 - 查新(After)
|
||||
public int Direction { get; set; } = 0;
|
||||
|
||||
public int Limit { get; set; } = 20;
|
||||
}
|
||||
}
|
||||
@ -7,12 +7,11 @@ namespace IM_API.Dtos
|
||||
// 使用 { get; init; } 确保对象创建后不可修改,且支持无参构造
|
||||
public MessageMsgType Type { get; init; } = default!;
|
||||
public ChatType ChatType { get; init; } = default!;
|
||||
public string? MsgId { get; init; }
|
||||
public Guid MsgId { get; init; }
|
||||
public int SenderId { get; init; }
|
||||
public int ReceiverId { get; init; }
|
||||
public int? GroupMemberId { get; init; }
|
||||
public string Content { get; init; } = default!;
|
||||
public DateTime TimeStamp { get; init; }
|
||||
public DateTimeOffset TimeStamp { get; init; }
|
||||
public MessageBaseDto() { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ namespace IM_API.Dtos.User
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账户状态
|
||||
|
||||
@ -18,6 +18,7 @@ namespace IM_API.Filters
|
||||
{
|
||||
if(context.Exception is BaseException)
|
||||
{
|
||||
_logger.LogWarning(context.Exception, context.Exception.Message);
|
||||
var exception = (BaseException)context.Exception;
|
||||
BaseResponse<object?> res = new BaseResponse<object?>(
|
||||
code:exception.Code,
|
||||
|
||||
@ -5,6 +5,7 @@ using IM_API.Dtos;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Security.Claims;
|
||||
|
||||
@ -40,16 +41,16 @@ namespace IM_API.Hubs
|
||||
}
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
public async Task<HubResponse<MessageBaseDto?>> SendMessage(MessageBaseDto dto)
|
||||
public async Task<HubResponse<MessageBaseVo?>> SendMessage(MessageBaseDto dto)
|
||||
{
|
||||
if (!Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
await Clients.Caller.SendAsync("ReceiveMessage", new BaseResponse<object?>(CodeDefine.AUTH_FAILED));
|
||||
Context.Abort();
|
||||
return new HubResponse<MessageBaseDto?>(CodeDefine.AUTH_FAILED, "SendMessage");
|
||||
return new HubResponse<MessageBaseVo?>(CodeDefine.AUTH_FAILED, "SendMessage");
|
||||
}
|
||||
var userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
MessageBaseDto msgInfo = null;
|
||||
MessageBaseVo? msgInfo = null;
|
||||
if(dto.ChatType == ChatType.PRIVATE)
|
||||
{
|
||||
msgInfo = await _messageService.SendPrivateMessageAsync(int.Parse(userIdStr), dto.ReceiverId, dto);
|
||||
@ -58,7 +59,7 @@ namespace IM_API.Hubs
|
||||
{
|
||||
msgInfo = await _messageService.SendGroupMessageAsync(int.Parse(userIdStr), dto.ReceiverId, dto);
|
||||
}
|
||||
return new HubResponse<MessageBaseDto?>("SendMessage", msgInfo);
|
||||
return new HubResponse<MessageBaseVo?>("SendMessage", msgInfo);
|
||||
}
|
||||
public async Task<HubResponse<object?>> ClearUnreadCount(int conversationId)
|
||||
{
|
||||
|
||||
@ -23,9 +23,11 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
||||
<PackageReference Include="RedLock.net" Version="2.3.2" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.9.32" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
|
||||
49
backend/IM_API/Interface/Services/ICacheService.cs
Normal file
49
backend/IM_API/Interface/Services/ICacheService.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
public interface ICacheService
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置缓存
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存对象类型</typeparam>
|
||||
/// <param name="key">缓存索引值</param>
|
||||
/// <param name="value">要缓存的对象</param>
|
||||
/// <param name="expiration">过期时间</param>
|
||||
/// <returns></returns>
|
||||
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
|
||||
/// <summary>
|
||||
/// 获取缓存
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key">缓存索引</param>
|
||||
/// <returns></returns>
|
||||
Task<T?> GetAsync<T>(string key);
|
||||
/// <summary>
|
||||
/// 删除缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存索引</param>
|
||||
/// <returns></returns>
|
||||
Task RemoveAsync(string key);
|
||||
/// <summary>
|
||||
/// 设置用户缓存
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
Task SetUserCacheAsync(User user);
|
||||
/// <summary>
|
||||
/// 通过用户名获取用户缓存
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
Task<User?> GetUserCacheAsync(string username);
|
||||
/// <summary>
|
||||
/// 删除用户缓存
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
Task RemoveUserCacheAsync(string username);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using IM_API.Dtos.Conversation;
|
||||
using IM_API.Models;
|
||||
using IM_API.VOs.Conversation;
|
||||
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
@ -22,7 +23,7 @@ namespace IM_API.Interface.Services
|
||||
/// </summary>
|
||||
/// <param name="userId">用户id</param>
|
||||
/// <returns></returns>
|
||||
Task<List<ConversationDto>> GetConversationsAsync(int userId);
|
||||
Task<List<ConversationVo>> GetConversationsAsync(int userId);
|
||||
/// <summary>
|
||||
/// 获取指定用户的所有推送标识符
|
||||
/// </summary>
|
||||
@ -34,7 +35,7 @@ namespace IM_API.Interface.Services
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <returns></returns>
|
||||
Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId);
|
||||
Task<ConversationVo> GetConversationByIdAsync(int userId, int conversationId);
|
||||
/// <summary>
|
||||
/// 清空未读消息
|
||||
/// </summary>
|
||||
@ -50,5 +51,6 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="chatType"></param>
|
||||
/// <returns></returns>
|
||||
Task MakeConversationAsync(int userAId, int userBId, ChatType chatType);
|
||||
Task UpdateConversationAfterSentAsync(UpdateConversationDto dto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,14 +11,14 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="groupId">群ID</param>
|
||||
/// <param name="userIds">邀请的用户列表</param>
|
||||
/// <returns></returns>
|
||||
Task InviteUsers(int userId,int groupId, List<int> userIds);
|
||||
Task InviteUsersAsync(int userId,int groupId, List<int> userIds);
|
||||
/// <summary>
|
||||
/// 加入群聊
|
||||
/// </summary>
|
||||
/// <param name="userId">操作者ID</param>
|
||||
/// <param name="groupId">群ID</param>
|
||||
/// <returns></returns>
|
||||
Task JoinGroup(int userId,int groupId);
|
||||
Task JoinGroupAsync(int userId,int groupId);
|
||||
/// <summary>
|
||||
/// 创建群聊
|
||||
/// </summary>
|
||||
@ -26,14 +26,22 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="groupCreateDto">群信息</param>
|
||||
/// <param name="userIds">邀请用户列表</param>
|
||||
/// <returns></returns>
|
||||
Task<GroupInfoDto> CreateGroup(int userId, GroupCreateDto groupCreateDto, List<int> userIds);
|
||||
Task<GroupInfoDto> CreateGroupAsync(int userId, GroupCreateDto groupCreateDto);
|
||||
/// <summary>
|
||||
/// 删除群
|
||||
/// </summary>
|
||||
/// <param name="userId">操作者ID</param>
|
||||
/// <param name="groupId">群ID</param>
|
||||
/// <returns></returns>
|
||||
Task DeleteGroup(int userId, int groupId);
|
||||
//Task UpdateGroupAuthori
|
||||
Task DeleteGroupAsync(int userId, int groupId);
|
||||
/// <summary>
|
||||
/// 获取当前用户群列表
|
||||
/// </summary>
|
||||
/// <param name="userId">操作者ID</param>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="limit"></param>
|
||||
/// <param name="desc"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Message;
|
||||
using IM_API.Models;
|
||||
using IM_API.VOs.Message;
|
||||
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
@ -11,7 +14,7 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="receiverId">接收人</param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
Task<MessageBaseDto> SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto);
|
||||
Task<MessageBaseVo> SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto);
|
||||
/// <summary>
|
||||
/// 发送群聊消息
|
||||
/// </summary>
|
||||
@ -19,16 +22,21 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="groupId">接收群id</param>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
Task<MessageBaseDto> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
|
||||
Task<MessageBaseVo> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
|
||||
/// <summary>
|
||||
/// 获取消息列表
|
||||
/// 消息入库
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
Task MakeMessageAsync(Message message);
|
||||
/// <summary>
|
||||
/// 获取历史消息列表
|
||||
/// </summary>
|
||||
/// <param name="conversationId">会话id(用于获取指定用户间聊天消息)</param>
|
||||
/// <param name="msgId">消息id</param>
|
||||
/// <param name="sequenceId">消息id</param>
|
||||
/// <param name="pageSize">获取消息数量</param>
|
||||
/// <param name="desc"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId,int? msgId,int? pageSize,bool desc);
|
||||
Task<List<MessageBaseVo>> GetMessagesAsync(int userId,MessageQueryDto dto);
|
||||
/// <summary>
|
||||
/// 获取未读消息数
|
||||
/// </summary>
|
||||
|
||||
12
backend/IM_API/Interface/Services/ISequenceIdService.cs
Normal file
12
backend/IM_API/Interface/Services/ISequenceIdService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
public interface ISequenceIdService
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建消息序号
|
||||
/// </summary>
|
||||
/// <param name="streamKey">聊天唯一标识/param>
|
||||
/// <returns></returns>
|
||||
Task<long> GetNextSquenceIdAsync(string streamKey);
|
||||
}
|
||||
}
|
||||
1094
backend/IM_API/Migrations/20260204140029_InitialCreate.Designer.cs
generated
Normal file
1094
backend/IM_API/Migrations/20260204140029_InitialCreate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
73
backend/IM_API/Migrations/20260204140029_InitialCreate.cs
Normal file
73
backend/IM_API/Migrations/20260204140029_InitialCreate.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "admins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "conversations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "devices");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "files");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "friend_request");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "friends");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "group_invite");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "group_member");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "group_request");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "login_log");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "permissionarole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "messages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "groups");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "roles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "permissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
1094
backend/IM_API/Migrations/20260204140708_change_file.Designer.cs
generated
Normal file
1094
backend/IM_API/Migrations/20260204140708_change_file.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
backend/IM_API/Migrations/20260204140708_change_file.cs
Normal file
28
backend/IM_API/Migrations/20260204140708_change_file.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class change_file : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Type",
|
||||
table: "files",
|
||||
newName: "FileType");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "FileType",
|
||||
table: "files",
|
||||
newName: "Type");
|
||||
}
|
||||
}
|
||||
}
|
||||
1093
backend/IM_API/Migrations/20260204150644_add-SequenceId.Designer.cs
generated
Normal file
1093
backend/IM_API/Migrations/20260204150644_add-SequenceId.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
backend/IM_API/Migrations/20260204150644_add-SequenceId.cs
Normal file
40
backend/IM_API/Migrations/20260204150644_add-SequenceId.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class addSequenceId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GroupMemberId",
|
||||
table: "messages");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "SequenceId",
|
||||
table: "messages",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SequenceId",
|
||||
table: "messages");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "GroupMemberId",
|
||||
table: "messages",
|
||||
type: "int(11)",
|
||||
nullable: true,
|
||||
comment: "若为群消息则表示具体的成员id");
|
||||
}
|
||||
}
|
||||
}
|
||||
1094
backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.Designer.cs
generated
Normal file
1094
backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class updateconversationlastreadmessageid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations",
|
||||
column: "lastReadMessageId",
|
||||
principalTable: "messages",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations",
|
||||
column: "lastReadMessageId",
|
||||
principalTable: "messages",
|
||||
principalColumn: "ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
1101
backend/IM_API/Migrations/20260205132459_update-message.Designer.cs
generated
Normal file
1101
backend/IM_API/Migrations/20260205132459_update-message.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
93
backend/IM_API/Migrations/20260205132459_update-message.cs
Normal file
93
backend/IM_API/Migrations/20260205132459_update-message.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class updatemessage : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "lastMessageId",
|
||||
table: "conversations",
|
||||
newName: "LastReadSequenceId");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "ClientMsgId",
|
||||
table: "messages",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MessageId",
|
||||
table: "conversations",
|
||||
type: "int(11)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_messages_SequenceId_StreamKey",
|
||||
table: "messages",
|
||||
columns: new[] { "SequenceId", "StreamKey" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_conversations_MessageId",
|
||||
table: "conversations",
|
||||
column: "MessageId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_conversations_messages_MessageId",
|
||||
table: "conversations",
|
||||
column: "MessageId",
|
||||
principalTable: "messages",
|
||||
principalColumn: "ID");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_conversations_messages_MessageId",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_messages_SequenceId_StreamKey",
|
||||
table: "messages");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_conversations_MessageId",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ClientMsgId",
|
||||
table: "messages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MessageId",
|
||||
table: "conversations");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "LastReadSequenceId",
|
||||
table: "conversations",
|
||||
newName: "lastMessageId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "conversations_ibfk_2",
|
||||
table: "conversations",
|
||||
column: "lastReadMessageId",
|
||||
principalTable: "messages",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
1101
backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.Designer.cs
generated
Normal file
1101
backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class updatedatetimeToDateTimeOffset : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1101
backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.Designer.cs
generated
Normal file
1101
backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class updateDateTimeOffsettype : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1098
backend/IM_API/Migrations/ImContextModelSnapshot.cs
Normal file
1098
backend/IM_API/Migrations/ImContextModelSnapshot.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -30,12 +30,12 @@ public partial class Admin
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime Updated { get; set; }
|
||||
public DateTimeOffset Updated { get; set; }
|
||||
|
||||
public virtual Role Role { get; set; } = null!;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -20,14 +21,14 @@ public partial class Conversation
|
||||
/// <summary>
|
||||
/// 最后一条未读消息ID
|
||||
/// </summary>
|
||||
public int? LastReadMessageId { get; set; }
|
||||
public long? LastReadSequenceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 未读消息数
|
||||
/// </summary>
|
||||
public int UnreadCount { get; set; }
|
||||
|
||||
public int ChatType { get; set; }
|
||||
public ChatType ChatType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息推送唯一标识符
|
||||
@ -42,9 +43,9 @@ public partial class Conversation
|
||||
/// <summary>
|
||||
/// 最后一条消息发送时间
|
||||
/// </summary>
|
||||
public DateTime LastMessageTime { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset LastMessageTime { get; set; }
|
||||
|
||||
public virtual Message? LastReadMessage { get; set; }
|
||||
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
18
backend/IM_API/Models/ConversationExt.cs
Normal file
18
backend/IM_API/Models/ConversationExt.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using IM_API.Dtos.Conversation;
|
||||
|
||||
namespace IM_API.Models
|
||||
{
|
||||
public partial class Conversation
|
||||
{
|
||||
public ChatType ChatTypeEnum {
|
||||
get
|
||||
{
|
||||
return ChatType;
|
||||
}
|
||||
set
|
||||
{
|
||||
ChatType = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -21,7 +22,8 @@ public partial class Device
|
||||
/// <summary>
|
||||
/// 最后一次登录
|
||||
/// </summary>
|
||||
public DateTime LastLogin { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset LastLogin { get; set; }
|
||||
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -25,7 +26,7 @@ public partial class File
|
||||
/// <summary>
|
||||
/// 文件类型
|
||||
/// </summary>
|
||||
public string Type { get; set; } = null!;
|
||||
public string FileType { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 关联消息ID
|
||||
@ -35,7 +36,8 @@ public partial class File
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual Message Message { get; set; } = null!;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -26,7 +27,8 @@ public partial class Friend
|
||||
/// <summary>
|
||||
/// 好友关系创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 好友备注名
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -20,7 +21,8 @@ public partial class FriendRequest
|
||||
/// <summary>
|
||||
/// 申请时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请附言
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -42,7 +43,8 @@ public partial class Group
|
||||
/// <summary>
|
||||
/// 群聊创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 群头像
|
||||
|
||||
@ -31,7 +31,7 @@ public partial class GroupInvite
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime? Created { get; set; }
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -25,7 +26,8 @@ public partial class GroupMember
|
||||
/// <summary>
|
||||
/// 加入群聊时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -31,7 +32,8 @@ public partial class GroupRequest
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ namespace IM_API.Models
|
||||
});
|
||||
modelBuilder.Entity<Conversation>(entity =>
|
||||
{
|
||||
|
||||
entity.Ignore(e => e.ChatTypeEnum);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Device>(entity =>
|
||||
|
||||
@ -101,7 +101,8 @@ public partial class ImContext : DbContext
|
||||
|
||||
entity.HasIndex(e => e.UserId, "Userid");
|
||||
|
||||
entity.HasIndex(e => e.LastReadMessageId, "lastMessageId");
|
||||
entity.HasIndex(e => e.LastReadSequenceId, "LastReadSequenceId");
|
||||
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int(11)")
|
||||
@ -113,7 +114,7 @@ public partial class ImContext : DbContext
|
||||
entity.Property(e => e.LastMessageTime)
|
||||
.HasComment("最后一条消息发送时间")
|
||||
.HasColumnType("datetime");
|
||||
entity.Property(e => e.LastReadMessageId)
|
||||
entity.Property(e => e.LastReadSequenceId)
|
||||
.HasComment("最后一条未读消息ID ")
|
||||
.HasColumnType("int(11)")
|
||||
.HasColumnName("lastReadMessageId");
|
||||
@ -130,10 +131,6 @@ public partial class ImContext : DbContext
|
||||
.HasComment("用户")
|
||||
.HasColumnType("int(11)");
|
||||
|
||||
entity.HasOne(d => d.LastReadMessage).WithMany(p => p.Conversations)
|
||||
.HasForeignKey(d => d.LastReadMessageId)
|
||||
.HasConstraintName("conversations_ibfk_2");
|
||||
|
||||
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
|
||||
.HasForeignKey(d => d.UserId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
@ -197,7 +194,7 @@ public partial class ImContext : DbContext
|
||||
entity.Property(e => e.Size)
|
||||
.HasComment("文件大小(单位:KB) ")
|
||||
.HasColumnType("int(11)");
|
||||
entity.Property(e => e.Type)
|
||||
entity.Property(e => e.FileType)
|
||||
.HasMaxLength(10)
|
||||
.HasComment("文件类型 ");
|
||||
entity.Property(e => e.Url)
|
||||
@ -529,6 +526,11 @@ public partial class ImContext : DbContext
|
||||
|
||||
entity.HasIndex(e => e.Sender, "Sender");
|
||||
|
||||
|
||||
//设置联合唯一索引
|
||||
entity.HasIndex(e => new { e.SequenceId, e.StreamKey })
|
||||
.IsUnique();
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int(11)")
|
||||
.HasColumnName("ID");
|
||||
@ -541,9 +543,6 @@ public partial class ImContext : DbContext
|
||||
entity.Property(e => e.Created)
|
||||
.HasComment("发送时间 ")
|
||||
.HasColumnType("datetime");
|
||||
entity.Property(e => e.GroupMemberId)
|
||||
.HasComment("若为群消息则表示具体的成员id")
|
||||
.HasColumnType("int(11)");
|
||||
entity.Property(e => e.MsgType)
|
||||
.HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)")
|
||||
.HasColumnType("tinyint(4)");
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -15,7 +16,8 @@ public partial class LoginLog
|
||||
/// <summary>
|
||||
/// 登录时间
|
||||
/// </summary>
|
||||
public DateTime Logined { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Logined { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录用户
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -18,6 +19,7 @@ public partial class Message
|
||||
/// (0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)
|
||||
/// </summary>
|
||||
public sbyte MsgType { get; set; }
|
||||
public Guid ClientMsgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容
|
||||
@ -42,7 +44,8 @@ public partial class Message
|
||||
/// <summary>
|
||||
/// 发送时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息推送唯一标识符
|
||||
@ -50,9 +53,10 @@ public partial class Message
|
||||
public string StreamKey { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 若为群消息则表示具体的成员id
|
||||
/// 消息排序标识
|
||||
/// </summary>
|
||||
public int? GroupMemberId { get; set; }
|
||||
|
||||
public long SequenceId { get; set; }
|
||||
|
||||
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -30,7 +31,8 @@ public partial class Notification
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -25,7 +26,8 @@ public partial class Permission
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual ICollection<Permissionarole> Permissionaroles { get; set; } = new List<Permissionarole>();
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -20,7 +21,8 @@ public partial class Role
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public virtual ICollection<Admin> Admins { get; set; } = new List<Admin>();
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace IM_API.Models;
|
||||
|
||||
@ -32,12 +33,13 @@ public partial class User
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改时间
|
||||
/// </summary>
|
||||
public DateTime? Updated { get; set; }
|
||||
[Column(TypeName = "datetimeoffset")]
|
||||
public DateTimeOffset? Updated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账户状态
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
|
||||
using IM_API.Configs;
|
||||
using IM_API.Configs.Options;
|
||||
using IM_API.Filters;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.Models;
|
||||
@ -25,18 +26,22 @@ namespace IM_API
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.Build();
|
||||
string conStr = configuration.GetConnectionString("DefaultConnection")!;
|
||||
string redisConStr = configuration.GetConnectionString("Redis");
|
||||
var conOptions = configuration.GetSection("ConnectionStrings").Get<ConnectionOptions>();
|
||||
//注入数据库上下文
|
||||
builder.Services.AddDbContext<ImContext>(options =>
|
||||
{
|
||||
options.UseMySql(conStr,ServerVersion.AutoDetect(conStr));
|
||||
options.UseMySql(conOptions.DefaultConnection,ServerVersion.AutoDetect(conOptions.DefaultConnection));
|
||||
});
|
||||
//注入redis
|
||||
var redis = ConnectionMultiplexer.Connect(redisConStr);
|
||||
var redis = ConnectionMultiplexer.Connect(conOptions.Redis);
|
||||
builder.Services.AddSingleton<IConnectionMultiplexer>(redis);
|
||||
|
||||
builder.Services.AddRabbitMQ(configuration.GetSection("RabbitMqOptions").Get<RabbitMqOptions>());
|
||||
builder.Services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.ConnectionMultiplexerFactory = () => Task.FromResult<IConnectionMultiplexer>(redis);
|
||||
});
|
||||
|
||||
builder.Services.AddRabbitMQ(configuration.GetSection("RabbitMqOptions").Get<RabbitMQOptions>());
|
||||
|
||||
builder.Services.AddAllService(configuration);
|
||||
|
||||
@ -116,7 +121,7 @@ namespace IM_API
|
||||
}).AddJsonOptions(options =>
|
||||
{
|
||||
// 保持 ISO 8601 格式
|
||||
options.JsonSerializerOptions.Converters.Add(new UtcDateTimeConverter());
|
||||
//options.JsonSerializerOptions.Converters.Add(new UtcDateTimeConverter());
|
||||
// 将枚举转换为字符串
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
// 建议:保持驼峰命名
|
||||
|
||||
@ -14,15 +14,19 @@ namespace IM_API.Services
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
public AuthService(ImContext context, ILogger<AuthService> logger, IMapper mapper)
|
||||
private readonly ICacheService _cache;
|
||||
public AuthService(ImContext context, ILogger<AuthService> logger, IMapper mapper, ICacheService cache)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<User> LoginAsync(LoginRequestDto dto)
|
||||
{
|
||||
var userinfo = await _cache.GetUserCacheAsync(dto.Username);
|
||||
if (userinfo != null && userinfo.Password == dto.Password) return userinfo;
|
||||
string username = dto.Username;
|
||||
string password = dto.Password;
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username && x.Password == password);
|
||||
@ -30,6 +34,7 @@ namespace IM_API.Services
|
||||
{
|
||||
throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
}
|
||||
await _cache.SetUserCacheAsync(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Conversation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
@ -27,26 +28,26 @@ namespace IM_API.Services
|
||||
|
||||
#endregion
|
||||
#region 获取用户会话列表
|
||||
public async Task<List<ConversationDto>> GetConversationsAsync(int userId)
|
||||
public async Task<List<ConversationVo>> GetConversationsAsync(int userId)
|
||||
{
|
||||
// 1. 获取私聊会话
|
||||
var privateList = await (from c in _context.Conversations
|
||||
join f in _context.Friends on new { c.UserId, c.TargetId }
|
||||
equals new { UserId = f.UserId, TargetId = f.FriendId }
|
||||
where c.UserId == userId && c.ChatType == (int)ChatType.PRIVATE
|
||||
where c.UserId == userId && c.ChatType == ChatType.PRIVATE
|
||||
select new { c, f.Avatar, f.RemarkName })
|
||||
.ToListAsync();
|
||||
|
||||
// 2. 获取群聊会话
|
||||
var groupList = await (from c in _context.Conversations
|
||||
join g in _context.Groups on c.TargetId equals g.Id
|
||||
where c.UserId == userId && c.ChatType == (int)ChatType.GROUP
|
||||
where c.UserId == userId && c.ChatType == ChatType.GROUP
|
||||
select new { c, g.Avatar, g.Name })
|
||||
.ToListAsync();
|
||||
|
||||
var privateDtos = privateList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationDto>(x.c);
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.RemarkName;
|
||||
return dto;
|
||||
@ -54,7 +55,7 @@ namespace IM_API.Services
|
||||
|
||||
var groupDtos = groupList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationDto>(x.c);
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.Name;
|
||||
return dto;
|
||||
@ -89,25 +90,23 @@ namespace IM_API.Services
|
||||
#endregion
|
||||
|
||||
#region 获取单个会话信息
|
||||
public async Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId)
|
||||
public async Task<ConversationVo> GetConversationByIdAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations
|
||||
.Include(x => x.LastReadMessage)
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.Id == conversationId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var dto = _mapper.Map<ConversationDto>(conversation);
|
||||
//dto.LastReadMessage = _mapper.Map<MessageBaseDto>(conversation);
|
||||
if(conversation.ChatType == (int)ChatType.PRIVATE)
|
||||
var dto = _mapper.Map<ConversationVo>(conversation);
|
||||
if(conversation.ChatType == ChatType.PRIVATE)
|
||||
{
|
||||
var friendInfo = await _context.Friends.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.FriendId == conversation.TargetId
|
||||
var friendInfo = await _context.Friends.Include(n => n.FriendNavigation).FirstOrDefaultAsync(
|
||||
x => x.UserId == conversation.UserId && x.FriendId == conversation.TargetId
|
||||
);
|
||||
if (friendInfo is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_mapper.Map(friendInfo,dto);
|
||||
}
|
||||
if(conversation.ChatType == (int)ChatType.GROUP)
|
||||
if(conversation.ChatType == ChatType.GROUP)
|
||||
{
|
||||
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == conversation.TargetId
|
||||
@ -125,12 +124,18 @@ namespace IM_API.Services
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var message = await _context.Messages
|
||||
.Where(x => x.StreamKey == conversation.StreamKey)
|
||||
.OrderByDescending(x => x.Id)
|
||||
.OrderByDescending(x => x.SequenceId)
|
||||
.FirstOrDefaultAsync();
|
||||
conversation.LastReadMessage = message;
|
||||
conversation.UnreadCount = 0;
|
||||
_context.Conversations.Update(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
if(message != null)
|
||||
{
|
||||
conversation.UnreadCount = 0;
|
||||
conversation.LastMessage = message.Content;
|
||||
conversation.LastReadSequenceId = message.SequenceId;
|
||||
conversation.LastMessageTime = message.Created;
|
||||
_context.Conversations.Update(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@ -143,10 +148,10 @@ namespace IM_API.Services
|
||||
StreamKeyBuilder.Private(userAId, userBId) : StreamKeyBuilder.Group(userBId);
|
||||
var conversation = new Conversation()
|
||||
{
|
||||
ChatType = (int)chatType,
|
||||
ChatType = chatType,
|
||||
LastMessage = "",
|
||||
LastMessageTime = DateTime.UtcNow,
|
||||
LastReadMessageId = -1,
|
||||
LastMessageTime = DateTime.Now,
|
||||
LastReadSequenceId = null,
|
||||
StreamKey = streamKey,
|
||||
TargetId = userBId,
|
||||
UnreadCount = 0,
|
||||
@ -156,5 +161,19 @@ namespace IM_API.Services
|
||||
_context.Conversations.Add(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task UpdateConversationAfterSentAsync(UpdateConversationDto dto)
|
||||
{
|
||||
var cList = await _context.Conversations.Where(x => x.StreamKey == dto.StreamKey).ToListAsync();
|
||||
foreach(var c in cList)
|
||||
{
|
||||
bool isSender = dto.SenderId == c.UserId;
|
||||
c.LastMessage = dto.LastMessage;
|
||||
c.LastMessageTime = dto.DateTime;
|
||||
c.LastReadSequenceId = isSender ? dto.LastSequenceId : c.LastReadSequenceId;
|
||||
c.UnreadCount = isSender ? 0 : c.UnreadCount + 1;
|
||||
}
|
||||
_context.Conversations.UpdateRange(cList);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,8 +132,8 @@ namespace IM_API.Services
|
||||
await _endpoint.Publish(new FriendAddEvent()
|
||||
{
|
||||
AggregateId = friendRequest.Id.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
Created = DateTime.UtcNow,
|
||||
OccurredAt = DateTime.Now,
|
||||
Created = DateTime.Now,
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = friendRequest.ResponseUser,
|
||||
RequestInfo = _mapper.Map<FriendRequestDto>(friendRequest),
|
||||
@ -183,7 +183,7 @@ namespace IM_API.Services
|
||||
await _endpoint.Publish(new RequestFriendEvent()
|
||||
{
|
||||
AggregateId = friendRequst.Id.ToString(),
|
||||
OccurredAt = friendRequst.Created,
|
||||
OccurredAt = friendRequst.Created.UtcDateTime,
|
||||
Description = friendRequst.Description,
|
||||
EventId = Guid.NewGuid(),
|
||||
FromUserId = friendRequst.RequestUser,
|
||||
@ -205,7 +205,7 @@ namespace IM_API.Services
|
||||
Friend friendA = new Friend()
|
||||
{
|
||||
Avatar = userbInfo.Avatar,
|
||||
Created = DateTime.UtcNow,
|
||||
Created = DateTime.Now,
|
||||
FriendId = userbInfo.Id,
|
||||
RemarkName = remarkName ?? userbInfo.NickName,
|
||||
StatusEnum = FriendStatus.Added,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Exceptions;
|
||||
@ -27,7 +28,7 @@ namespace IM_API.Services
|
||||
|
||||
private async Task<List<GroupInvite>> GetGroupInvites(int userId, int groupId, List<int> ids)
|
||||
{
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
DateTime dateTime = DateTime.Now;
|
||||
//验证被邀请用户是否为好友
|
||||
var validFriendIds = await _context.Friends
|
||||
.Where(f => f.UserId == userId && ids.Contains(f.FriendId))
|
||||
@ -44,13 +45,14 @@ namespace IM_API.Services
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<GroupInfoDto> CreateGroup(int userId, GroupCreateDto groupCreateDto, List<int> userIds)
|
||||
public async Task<GroupInfoDto> CreateGroupAsync(int userId, GroupCreateDto groupCreateDto)
|
||||
{
|
||||
List<int> userIds = groupCreateDto.UserIDs ?? [];
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
//先创建群
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
DateTime dateTime = DateTime.Now;
|
||||
Group group = _mapper.Map<Group>(groupCreateDto);
|
||||
group.GroupMaster = userId;
|
||||
_context.Groups.Add(group);
|
||||
@ -90,21 +92,38 @@ namespace IM_API.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteGroup(int userId, int groupId)
|
||||
public Task DeleteGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task InviteUsers(int userId, int groupId, List<int> userIds)
|
||||
public async Task InviteUsersAsync(int userId, int groupId, List<int> userIds)
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == groupId) ?? throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
|
||||
}
|
||||
|
||||
public Task JoinGroup(int userId, int groupId)
|
||||
public Task JoinGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.GroupMembers
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(s => s.Group);
|
||||
if (desc)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
}
|
||||
var list = await query
|
||||
.Skip((page - 1) * limit)
|
||||
.Take(limit)
|
||||
.ProjectTo<GroupInfoDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ namespace IM_API.Services
|
||||
|
||||
public (string token, DateTime expiresAt) CreateAccessTokenForUser(int userId, string username, string role)
|
||||
{
|
||||
var expiresAt = DateTime.UtcNow.AddMinutes(_accessMinutes);
|
||||
var expiresAt = DateTime.Now.AddMinutes(_accessMinutes);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
|
||||
@ -2,12 +2,18 @@
|
||||
using IM_API.Application.Interfaces;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Message;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.RegularExpressions;
|
||||
using static MassTransit.Monitoring.Performance.BuiltInCounters;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
@ -19,9 +25,10 @@ namespace IM_API.Services
|
||||
//废弃,此处已使用rabbitMQ替代
|
||||
//private readonly IEventBus _eventBus;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
private readonly ISequenceIdService _sequenceIdService;
|
||||
public MessageService(
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper, IEventBus eventBus,
|
||||
IPublishEndpoint publishEndpoint
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper,
|
||||
IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService
|
||||
)
|
||||
{
|
||||
_context = context;
|
||||
@ -29,40 +36,43 @@ namespace IM_API.Services
|
||||
_mapper = mapper;
|
||||
//_eventBus = eventBus;
|
||||
_endpoint = publishEndpoint;
|
||||
_sequenceIdService = sequenceIdService;
|
||||
}
|
||||
|
||||
public async Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc)
|
||||
public async Task<List<MessageBaseVo>> GetMessagesAsync(int userId,MessageQueryDto dto)
|
||||
{
|
||||
//获取会话信息,用于获取双方聊天的唯一标识streamkey
|
||||
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.Id == conversationId && x.UserId == userId
|
||||
x => x.Id == dto.ConversationId && x.UserId == userId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var query = _context.Messages.AsQueryable();
|
||||
if(msgId != null)
|
||||
|
||||
var baseQuery = _context.Messages.Where(x => x.StreamKey == conversation.StreamKey);
|
||||
if (dto.Direction == 0) // Before: 找比锚点小的,按倒序排
|
||||
{
|
||||
query = query.Where(
|
||||
x => x.StreamKey == conversation.StreamKey && x.Id < msgId.Value
|
||||
)
|
||||
.OrderByDescending(x => x.Id);
|
||||
if (dto.Cursor.HasValue)
|
||||
baseQuery = baseQuery.Where(m => m.SequenceId < dto.Cursor.Value);
|
||||
|
||||
var list = await baseQuery
|
||||
.OrderByDescending(m => m.SequenceId) // 最新消息在最前
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
|
||||
return list.OrderBy(s => s.SequenceId).ToList();
|
||||
}
|
||||
else
|
||||
else // After: 找比锚点大的,按正序排(用于补洞或刷新)
|
||||
{
|
||||
query = query.Where(
|
||||
x => x.StreamKey == conversation.StreamKey && x.Id > conversation.LastReadMessageId
|
||||
);
|
||||
// 如果 Cursor 为空且是 After,逻辑上说不通,通常直接返回空或报错
|
||||
if (!dto.Cursor.HasValue) return new List<MessageBaseVo>();
|
||||
|
||||
return await baseQuery
|
||||
.Where(m => m.SequenceId > dto.Cursor.Value)
|
||||
.OrderBy(m => m.SequenceId) // 按时间线正序
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
}
|
||||
if(pageSize != null)
|
||||
{
|
||||
query = query.Take(pageSize.Value);
|
||||
}
|
||||
var msgList = await query
|
||||
.ToListAsync();
|
||||
msgList = msgList
|
||||
.OrderBy(x => x.Created)
|
||||
.ThenBy(t => t.Id)
|
||||
.ToList();
|
||||
return _mapper.Map<List<MessageBaseDto>>(msgList);
|
||||
}
|
||||
|
||||
public Task<int> GetUnreadCountAsync(int userId)
|
||||
@ -89,8 +99,14 @@ namespace IM_API.Services
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task MakeMessageAsync(Message message)
|
||||
{
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
#region 发送群消息
|
||||
public async Task<MessageBaseDto> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
public async Task<MessageBaseVo> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
{
|
||||
//判断群存在
|
||||
var isExist = await _context.Groups.AnyAsync(x => x.Id == groupId);
|
||||
@ -99,30 +115,23 @@ namespace IM_API.Services
|
||||
var isMember = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == senderId);
|
||||
if (!isMember) throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.Sender = senderId;
|
||||
message.StreamKey = StreamKeyBuilder.Group(
|
||||
|
||||
groupId);
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
message.StreamKey = StreamKeyBuilder.Group(groupId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseDto>(message);
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
|
||||
}
|
||||
#endregion
|
||||
#region 发送私聊消息
|
||||
public async Task<MessageBaseDto> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
|
||||
public async Task<MessageBaseVo> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
|
||||
{
|
||||
bool isExist = await _context.Friends.AnyAsync(x => x.FriendId == receiverId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.Sender = senderId;
|
||||
//生成消息流唯一标识符
|
||||
message.StreamKey = StreamKeyBuilder.Private(dto.SenderId, dto.ReceiverId);
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
message.StreamKey = StreamKeyBuilder.Private(senderId, receiverId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseDto>(message);
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
62
backend/IM_API/Services/RedisCacheService.cs
Normal file
62
backend/IM_API/Services/RedisCacheService.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class RedisCacheService:ICacheService
|
||||
{
|
||||
private readonly IDistributedCache _cache;
|
||||
public RedisCacheService(IDistributedCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
var valueBytes= await _cache.GetAsync(key);
|
||||
if (valueBytes is null || valueBytes.Length == 0) return default;
|
||||
return JsonSerializer.Deserialize<T>(valueBytes);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return default;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
return await GetAsync<User>(key);
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key) => await _cache.RemoveAsync(key);
|
||||
|
||||
public async Task RemoveUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
await RemoveAsync(key);
|
||||
}
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
|
||||
{
|
||||
var options = new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromHours(1)
|
||||
};
|
||||
var valueBytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
await _cache.SetAsync(key, valueBytes, options);
|
||||
}
|
||||
|
||||
public async Task SetUserCacheAsync(User user)
|
||||
{
|
||||
var idKey = RedisKeys.GetUserinfoKey(user.Id.ToString());
|
||||
await SetAsync(idKey, user);
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(user.Username);
|
||||
await SetAsync(usernameKey, user.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
46
backend/IM_API/Services/SequenceIdService.cs
Normal file
46
backend/IM_API/Services/SequenceIdService.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RedLockNet;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class SequenceIdService : ISequenceIdService
|
||||
{
|
||||
private IDatabase _database;
|
||||
private IDistributedLockFactory _lockFactory;
|
||||
private ImContext _context;
|
||||
public SequenceIdService(IConnectionMultiplexer connectionMultiplexer,
|
||||
IDistributedLockFactory distributedLockFactory, ImContext imContext)
|
||||
{
|
||||
_database = connectionMultiplexer.GetDatabase();
|
||||
_lockFactory = distributedLockFactory;
|
||||
_context = imContext;
|
||||
}
|
||||
public async Task<long> GetNextSquenceIdAsync(string streamKey)
|
||||
{
|
||||
string key = RedisKeys.GetSequenceIdKey(streamKey);
|
||||
string lockKey = RedisKeys.GetSequenceIdLockKey(streamKey);
|
||||
var exists = await _database.KeyExistsAsync(key);
|
||||
if (!exists)
|
||||
{
|
||||
using (var _lock = await _lockFactory.CreateLockAsync(lockKey, TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
if (_lock.IsAcquired)
|
||||
{
|
||||
if(!await _database.KeyExistsAsync(key))
|
||||
{
|
||||
var max = await _context.Messages
|
||||
.Where(x => x.StreamKey == streamKey)
|
||||
.MaxAsync(m => (long?)m.SequenceId) ?? 0;
|
||||
await _database.StringSetAsync(key, max, TimeSpan.FromDays(7));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return await _database.StringIncrementAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
@ -13,31 +14,43 @@ namespace IM_API.Services
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
public UserService(ImContext imContext,ILogger<UserService> logger,IMapper mapper)
|
||||
private readonly ICacheService _cacheService;
|
||||
public UserService(ImContext imContext,ILogger<UserService> logger,IMapper mapper, ICacheService cacheService)
|
||||
{
|
||||
this._context = imContext;
|
||||
this._logger = logger;
|
||||
this._mapper = mapper;
|
||||
_cacheService = cacheService;
|
||||
}
|
||||
#region 获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoAsync(int userId)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
//查询redis缓存,如果存在直接返回不走查库逻辑
|
||||
var key = RedisKeys.GetUserinfoKey(userId.ToString());
|
||||
var userinfoCache = await _cacheService.GetAsync<User>(key);
|
||||
if (userinfoCache != null) return _mapper.Map<UserInfoDto>(userinfoCache);
|
||||
//无缓存查库
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户名获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoByUsernameAsync(string username)
|
||||
{
|
||||
var userinfo = await _cacheService.GetUserCacheAsync(username);
|
||||
if (userinfo != null) return _mapper.Map<UserInfoDto>(userinfo);
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
@ -70,6 +83,7 @@ namespace IM_API.Services
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
_mapper.Map(dto,user);
|
||||
await _context.SaveChangesAsync();
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
|
||||
22
backend/IM_API/Tools/RedisKeys.cs
Normal file
22
backend/IM_API/Tools/RedisKeys.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace IM_API.Tools
|
||||
{
|
||||
public static class RedisKeys
|
||||
{
|
||||
public static string GetUserinfoKey(string userId)
|
||||
{
|
||||
return $"user::uinfo::{userId}";
|
||||
}
|
||||
public static string GetUserinfoKeyByUsername(string username)
|
||||
{
|
||||
return $"user::uinfobyid::{username}";
|
||||
}
|
||||
public static string GetSequenceIdKey(string streamKey)
|
||||
{
|
||||
return $"chat::seq::{streamKey}";
|
||||
}
|
||||
public static string GetSequenceIdLockKey(string streamKey)
|
||||
{
|
||||
return $"lock::seq::{streamKey}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user