diff --git a/backend/IMTest/Service/GroupServiceTest.cs b/backend/IMTest/Service/GroupServiceTest.cs new file mode 100644 index 0000000..fed2ebe --- /dev/null +++ b/backend/IMTest/Service/GroupServiceTest.cs @@ -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> _loggerMock = new(); + private readonly Mock _publishMock = new(); + + public GroupServiceTests() + { + // 1. 配置内存数据库 + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + _context = new ImContext(options); + + // 2. 配置真实的 AutoMapper (ProjectTo 必须使用真实配置,不能用 Mock) + var config = new MapperConfiguration(cfg => + { + // 替换为你项目中真实的 Profile 类名 + cfg.AddProfile(); + // 如果有多个 Profile,可以继续添加或者加载整个程序集 + // cfg.AddMaps(typeof(GroupProfile).Assembly); + }); + _mapper = config.CreateMapper(); + } + + [Fact] + public async Task GetGroupList_ShouldReturnPagedAndOrderedData() + { + // Arrange (准备数据) + var userId = 1; + var groups = new List + { + 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 + { + 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); + } + } +} \ No newline at end of file diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.deps.json b/backend/IMTest/bin/Debug/net8.0/IMTest.deps.json index 63c1c96..8ceed33 100644 --- a/backend/IMTest/bin/Debug/net8.0/IMTest.deps.json +++ b/backend/IMTest/bin/Debug/net8.0/IMTest.deps.json @@ -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, diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.dll b/backend/IMTest/bin/Debug/net8.0/IMTest.dll index 0b2a470..c459494 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IMTest.dll and b/backend/IMTest/bin/Debug/net8.0/IMTest.dll differ diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.pdb b/backend/IMTest/bin/Debug/net8.0/IMTest.pdb index 6bac1a3..fe0d0e0 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IMTest.pdb and b/backend/IMTest/bin/Debug/net8.0/IMTest.pdb differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.deps.json b/backend/IMTest/bin/Debug/net8.0/IM_API.deps.json index 28566e9..b54ca2c 100644 --- a/backend/IMTest/bin/Debug/net8.0/IM_API.deps.json +++ b/backend/IMTest/bin/Debug/net8.0/IM_API.deps.json @@ -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, diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.dll b/backend/IMTest/bin/Debug/net8.0/IM_API.dll index 3da801b..4849539 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.dll and b/backend/IMTest/bin/Debug/net8.0/IM_API.dll differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.exe b/backend/IMTest/bin/Debug/net8.0/IM_API.exe index 65fde7e..9405e80 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.exe and b/backend/IMTest/bin/Debug/net8.0/IM_API.exe differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.pdb b/backend/IMTest/bin/Debug/net8.0/IM_API.pdb index e74275f..ed2fc4e 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.pdb and b/backend/IMTest/bin/Debug/net8.0/IM_API.pdb differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs index ffdca41..8fe4957 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs @@ -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")] diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache index 44b9dd3..eee5ee2 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache @@ -1 +1 @@ -ee1fc45f192938903a153f1c2e3b53f60a2184cb806b87d9b57b487095b98264 +ed76e92b5e8795c53e98fa0f534a65801467f19a9f8a7b0c127cdc4e442cbb2a diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.assets.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.assets.cache index 5432714..b61f83e 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.assets.cache and b/backend/IMTest/obj/Debug/net8.0/IMTest.assets.cache differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache index be9dd6e..c2e9704 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache and b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.CoreCompileInputs.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.CoreCompileInputs.cache index 290ddcd..a0be04a 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.CoreCompileInputs.cache +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -97ad978fabe5fb8f7f258c9878659ee9a5f2492332ad5efd70b1d456ebc42a59 +f5bc8c7a1703bdc5e206f40b9687382b19faace8ff21b9d0a842dec47bff95a8 diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.FileListAbsolute.txt b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.FileListAbsolute.txt index 6923254..9544605 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.FileListAbsolute.txt +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.FileListAbsolute.txt @@ -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 diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/IMTest.dll index 0b2a470..c459494 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/IMTest.dll differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.pdb b/backend/IMTest/obj/Debug/net8.0/IMTest.pdb index 6bac1a3..fe0d0e0 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.pdb and b/backend/IMTest/obj/Debug/net8.0/IMTest.pdb differ diff --git a/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll index 8cd8d14..3aa11d3 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll differ diff --git a/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll index 8cd8d14..3aa11d3 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll differ diff --git a/backend/IMTest/obj/IMTest.csproj.nuget.dgspec.json b/backend/IMTest/obj/IMTest.csproj.nuget.dgspec.json index 3a75a9e..7422270 100644 --- a/backend/IMTest/obj/IMTest.csproj.nuget.dgspec.json +++ b/backend/IMTest/obj/IMTest.csproj.nuget.dgspec.json @@ -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, )" diff --git a/backend/IMTest/obj/IMTest.csproj.nuget.g.targets b/backend/IMTest/obj/IMTest.csproj.nuget.g.targets index c7ce45a..6d8161d 100644 --- a/backend/IMTest/obj/IMTest.csproj.nuget.g.targets +++ b/backend/IMTest/obj/IMTest.csproj.nuget.g.targets @@ -2,8 +2,8 @@ - - + + diff --git a/backend/IMTest/obj/project.assets.json b/backend/IMTest/obj/project.assets.json index 04dbca1..4e1d539 100644 --- a/backend/IMTest/obj/project.assets.json +++ b/backend/IMTest/obj/project.assets.json @@ -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": { diff --git a/backend/IMTest/obj/project.nuget.cache b/backend/IMTest/obj/project.nuget.cache index 8ba4b76..e5a6826 100644 --- a/backend/IMTest/obj/project.nuget.cache +++ b/backend/IMTest/obj/project.nuget.cache @@ -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", diff --git a/backend/IM_API/Application/EventHandlers/FriendAddHandler/FriendAddSignalRHandler.cs b/backend/IM_API/Application/EventHandlers/FriendAddHandler/FriendAddSignalRHandler.cs index 77e046c..e692035 100644 --- a/backend/IM_API/Application/EventHandlers/FriendAddHandler/FriendAddSignalRHandler.cs +++ b/backend/IM_API/Application/EventHandlers/FriendAddHandler/FriendAddSignalRHandler.cs @@ -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); } diff --git a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs index 405f728..d4aab33 100644 --- a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs +++ b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs @@ -35,28 +35,15 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler public async Task Consume(ConsumeContext 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 + }); } } diff --git a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/MessageCreatedDbHandler.cs b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/MessageCreatedDbHandler.cs new file mode 100644 index 0000000..d926a81 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/MessageCreatedDbHandler.cs @@ -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 + { + private readonly IMessageSevice _messageService; + public readonly IMapper _mapper; + public MessageCreatedDbHandler(IMessageSevice messageSevice, IMapper mapper) + { + _messageService = messageSevice; + _mapper = mapper; + } + + public async Task Consume(ConsumeContext context) + { + var @event = context.Message; + var msg = _mapper.Map(@event); + await _messageService.MakeMessageAsync(msg); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs index a103658..b2a2e0e 100644 --- a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs +++ b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs @@ -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 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(@event); + + // 再从实体转 VO,这是最稳妥的路径,因为这两者的映射你肯定配过了 + var messageBaseVo = _mapper.Map(entity); + + // 2. 打印日志确认逻辑执行到这里了 + Console.WriteLine($"[SignalR] 准备向所有人广播消息: {messageBaseVo.Content}"); + + // 3. 执行广播 + await _hub.Clients.User(@event.MsgRecipientId.ToString()).SendAsync("ReceiveMessage", new HubResponse("Event", messageBaseVo)); + + Console.WriteLine("[SignalR] 广播指令已发出"); + Console.ResetColor(); + } + catch (Exception ex) + { + Console.WriteLine($"[SignalR] 发送失败: {ex.Message}"); + Console.ResetColor(); + throw; // 抛出异常触发 MassTransit 重试 } } } diff --git a/backend/IM_API/Configs/MQConfig.cs b/backend/IM_API/Configs/MQConfig.cs index b694705..8fcbf2d 100644 --- a/backend/IM_API/Configs/MQConfig.cs +++ b/backend/IM_API/Configs/MQConfig.cs @@ -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(); x.AddConsumer(); x.AddConsumer(); - + x.AddConsumer(); 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(); + r.Handle(); + r.Ignore(); + 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; } - } + } diff --git a/backend/IM_API/Configs/MapperConfig.cs b/backend/IM_API/Configs/MapperConfig.cs index a117a54..9ba80cc 100644 --- a/backend/IM_API/Configs/MapperConfig.cs +++ b/backend/IM_API/Configs/MapperConfig.cs @@ -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() .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() - .ForMember(dest => dest.Type , opt => opt.MapFrom(src => src.MsgTypeEnum.ToString())) - .ForMember(dest => dest.MsgId , opt => opt.MapFrom(src => src.Id)) + CreateMap() + .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() .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() + .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() - .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() + CreateMap() .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() - .ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar)) + CreateMap() + .ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.FriendNavigation.Avatar)) .ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.RemarkName)); - CreateMap() + CreateMap() .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() .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)) + ; } } } diff --git a/backend/IM_API/Configs/Options/ConnectionOptions.cs b/backend/IM_API/Configs/Options/ConnectionOptions.cs new file mode 100644 index 0000000..53e1d3b --- /dev/null +++ b/backend/IM_API/Configs/Options/ConnectionOptions.cs @@ -0,0 +1,8 @@ +namespace IM_API.Configs.Options +{ + public class ConnectionOptions + { + public string DefaultConnection { get; set; } + public string Redis { get; set; } + } +} diff --git a/backend/IM_API/Configs/Options/RabbitMQOptions.cs b/backend/IM_API/Configs/Options/RabbitMQOptions.cs new file mode 100644 index 0000000..045cb1d --- /dev/null +++ b/backend/IM_API/Configs/Options/RabbitMQOptions.cs @@ -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; } + } +} diff --git a/backend/IM_API/Configs/ServiceCollectionExtensions.cs b/backend/IM_API/Configs/ServiceCollectionExtensions.cs index 770b8ac..4823103 100644 --- a/backend/IM_API/Configs/ServiceCollectionExtensions.cs +++ b/backend/IM_API/Configs/ServiceCollectionExtensions.cs @@ -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(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(sp => + { + var connection = sp.GetRequiredService(); + // 这里可以配置多个 Redis 节点提高安全性,单机运行传一个即可 + return RedLockFactory.Create(new List { 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(CodeDefine.PARAMETER_ERROR.Code, errors.First().Message)); }; }); diff --git a/backend/IM_API/Controllers/AuthController.cs b/backend/IM_API/Controllers/AuthController.cs index a9fbf3a..2e7cf0d 100644 --- a/backend/IM_API/Controllers/AuthController.cs +++ b/backend/IM_API/Controllers/AuthController.cs @@ -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(new LoginDto(userInfo,token,refreshToken, expiresAt)); + var res = new BaseResponse(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),StatusCodes.Status200OK)] + [ProducesResponseType(typeof(BaseResponse),StatusCodes.Status200OK)] public async Task Refresh(RefreshDto dto) { (bool ok,int userId) = await _refreshTokenService.ValidateRefreshTokenAsync(dto.refreshToken); if (!ok) { - var err = new BaseResponse(CodeDefine.AUTH_FAILED); + var err = new BaseResponse(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(new LoginDto(userinfo,token, dto.refreshToken, expiresAt)); + var res = new BaseResponse(new LoginVo(userinfo,token, dto.refreshToken, expiresAt)); return Ok(res); } } diff --git a/backend/IM_API/Controllers/ConversationController.cs b/backend/IM_API/Controllers/ConversationController.cs index 311b106..4b72791 100644 --- a/backend/IM_API/Controllers/ConversationController.cs +++ b/backend/IM_API/Controllers/ConversationController.cs @@ -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); + var res = new BaseResponse>(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(conversation); + var res = new BaseResponse(conversation); return Ok(res); } [HttpPost] diff --git a/backend/IM_API/Controllers/GroupController.cs b/backend/IM_API/Controllers/GroupController.cs new file mode 100644 index 0000000..3c81ec8 --- /dev/null +++ b/backend/IM_API/Controllers/GroupController.cs @@ -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 _logger; + public GroupController(IGroupService groupService, ILogger logger) + { + _groupService = groupService; + _logger = logger; + } + [HttpGet] + [ProducesResponseType(typeof(BaseResponse>) ,StatusCodes.Status200OK)] + public async Task 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); + return Ok(res); + } + + [HttpPost] + [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] + public async Task CreateGroup([FromBody]GroupCreateDto groupCreateDto) + { + var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier); + var groupInfo = await _groupService.CreateGroupAsync(int.Parse(userIdStr), groupCreateDto); + var res = new BaseResponse(groupInfo); + return Ok(res); + } + } +} diff --git a/backend/IM_API/Controllers/MessageController.cs b/backend/IM_API/Controllers/MessageController.cs index bfc40b6..be09207 100644 --- a/backend/IM_API/Controllers/MessageController.cs +++ b/backend/IM_API/Controllers/MessageController.cs @@ -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 _logger; - public MessageController(IMessageSevice messageService, ILogger logger) + private readonly IEventBus _eventBus; + public MessageController(IMessageSevice messageService, ILogger logger, IEventBus eventBus) { _messageService = messageService; _logger = logger; + _eventBus = eventBus; } [HttpPost] - public async Task SendPrivateMessage(MessageBaseDto dto) + [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] + public async Task SendMessage(MessageBaseDto dto) { var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier); - await _messageService.SendPrivateMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto); - return Ok(new BaseResponse()); - } - [HttpPost] - public async Task SendGroupMessage(MessageBaseDto dto) - { - var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier); - await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto); - return Ok(new BaseResponse()); + 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)); } [HttpGet] - public async Task GetMessageList([Required]int conversationId, int? msgId, int? pageSize) + [ProducesResponseType(typeof(BaseResponse>), StatusCodes.Status200OK)] + public async Task 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>(msgList); + var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr),dto); + var res = new BaseResponse>(msgList); return Ok(res); } } diff --git a/backend/IM_API/Domain/Events/DomainEvent.cs b/backend/IM_API/Domain/Events/DomainEvent.cs index 7a5f335..3f2b8df 100644 --- a/backend/IM_API/Domain/Events/DomainEvent.cs +++ b/backend/IM_API/Domain/Events/DomainEvent.cs @@ -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; } diff --git a/backend/IM_API/Domain/Events/FriendAddEvent.cs b/backend/IM_API/Domain/Events/FriendAddEvent.cs index d2e6378..6724f6c 100644 --- a/backend/IM_API/Domain/Events/FriendAddEvent.cs +++ b/backend/IM_API/Domain/Events/FriendAddEvent.cs @@ -19,7 +19,7 @@ namespace IM_API.Domain.Events /// /// 好友关系创建时间 /// - public DateTime Created { get; init; } + public DateTimeOffset Created { get; init; } } } diff --git a/backend/IM_API/Domain/Events/MessageCreatedEvent.cs b/backend/IM_API/Domain/Events/MessageCreatedEvent.cs index c720dd5..b35e1c0 100644 --- a/backend/IM_API/Domain/Events/MessageCreatedEvent.cs +++ b/backend/IM_API/Domain/Events/MessageCreatedEvent.cs @@ -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; } diff --git a/backend/IM_API/Dtos/Conversation/UpdateConversationDto.cs b/backend/IM_API/Dtos/Conversation/UpdateConversationDto.cs new file mode 100644 index 0000000..8313a12 --- /dev/null +++ b/backend/IM_API/Dtos/Conversation/UpdateConversationDto.cs @@ -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; } + } +} diff --git a/backend/IM_API/Dtos/Friend/FriendDto.cs b/backend/IM_API/Dtos/Friend/FriendDto.cs index 8f2ecb5..f0d5269 100644 --- a/backend/IM_API/Dtos/Friend/FriendDto.cs +++ b/backend/IM_API/Dtos/Friend/FriendDto.cs @@ -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; diff --git a/backend/IM_API/Dtos/Friend/FriendRequestResDto.cs b/backend/IM_API/Dtos/Friend/FriendRequestResDto.cs index 45e94dd..6fcde65 100644 --- a/backend/IM_API/Dtos/Friend/FriendRequestResDto.cs +++ b/backend/IM_API/Dtos/Friend/FriendRequestResDto.cs @@ -21,7 +21,7 @@ namespace IM_API.Dtos.Friend /// /// 申请时间 /// - public DateTime Created { get; set; } + public DateTimeOffset Created { get; set; } /// /// 申请附言 diff --git a/backend/IM_API/Dtos/Group/GroupCreateDto.cs b/backend/IM_API/Dtos/Group/GroupCreateDto.cs index 208371c..32b2941 100644 --- a/backend/IM_API/Dtos/Group/GroupCreateDto.cs +++ b/backend/IM_API/Dtos/Group/GroupCreateDto.cs @@ -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 /// /// 群聊名称 /// + [Required(ErrorMessage = "群名称必填")] + [MaxLength(20, ErrorMessage = "群名称不能大于20字符")] public string Name { get; set; } = null!; /// /// 群头像 /// + [Required(ErrorMessage = "群头像必填")] public string Avatar { get; set; } = null!; + public List? UserIDs { get; set; } } } diff --git a/backend/IM_API/Dtos/Group/GroupInfoDto.cs b/backend/IM_API/Dtos/Group/GroupInfoDto.cs index c9d8d9c..3265681 100644 --- a/backend/IM_API/Dtos/Group/GroupInfoDto.cs +++ b/backend/IM_API/Dtos/Group/GroupInfoDto.cs @@ -41,7 +41,7 @@ namespace IM_API.Dtos.Group /// /// 群聊创建时间 /// - public DateTime Created { get; set; } + public DateTimeOffset Created { get; set; } /// /// 群头像 diff --git a/backend/IM_API/Dtos/HubResponse.cs b/backend/IM_API/Dtos/HubResponse.cs index eb167bf..18c2c0e 100644 --- a/backend/IM_API/Dtos/HubResponse.cs +++ b/backend/IM_API/Dtos/HubResponse.cs @@ -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) { diff --git a/backend/IM_API/Dtos/Message/MessageQueryDto.cs b/backend/IM_API/Dtos/Message/MessageQueryDto.cs new file mode 100644 index 0000000..607ec1f --- /dev/null +++ b/backend/IM_API/Dtos/Message/MessageQueryDto.cs @@ -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; + } +} diff --git a/backend/IM_API/Dtos/MessageDto.cs b/backend/IM_API/Dtos/MessageDto.cs index a546b89..3606c48 100644 --- a/backend/IM_API/Dtos/MessageDto.cs +++ b/backend/IM_API/Dtos/MessageDto.cs @@ -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() { } } } diff --git a/backend/IM_API/Dtos/User/UserInfoDto.cs b/backend/IM_API/Dtos/User/UserInfoDto.cs index f77bb24..e530be6 100644 --- a/backend/IM_API/Dtos/User/UserInfoDto.cs +++ b/backend/IM_API/Dtos/User/UserInfoDto.cs @@ -32,7 +32,7 @@ namespace IM_API.Dtos.User /// /// 创建时间 /// - public DateTime Created { get; set; } + public DateTimeOffset Created { get; set; } /// /// 账户状态 diff --git a/backend/IM_API/Filters/GlobalExceptionFilter.cs b/backend/IM_API/Filters/GlobalExceptionFilter.cs index 7a0c726..e1af44e 100644 --- a/backend/IM_API/Filters/GlobalExceptionFilter.cs +++ b/backend/IM_API/Filters/GlobalExceptionFilter.cs @@ -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 res = new BaseResponse( code:exception.Code, diff --git a/backend/IM_API/Hubs/ChatHub.cs b/backend/IM_API/Hubs/ChatHub.cs index da23832..3b3d857 100644 --- a/backend/IM_API/Hubs/ChatHub.cs +++ b/backend/IM_API/Hubs/ChatHub.cs @@ -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> SendMessage(MessageBaseDto dto) + public async Task> SendMessage(MessageBaseDto dto) { if (!Context.User.Identity.IsAuthenticated) { await Clients.Caller.SendAsync("ReceiveMessage", new BaseResponse(CodeDefine.AUTH_FAILED)); Context.Abort(); - return new HubResponse(CodeDefine.AUTH_FAILED, "SendMessage"); + return new HubResponse(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("SendMessage", msgInfo); + return new HubResponse("SendMessage", msgInfo); } public async Task> ClearUnreadCount(int conversationId) { diff --git a/backend/IM_API/IM_API.csproj b/backend/IM_API/IM_API.csproj index b47084f..9f720ca 100644 --- a/backend/IM_API/IM_API.csproj +++ b/backend/IM_API/IM_API.csproj @@ -23,9 +23,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/backend/IM_API/Interface/Services/ICacheService.cs b/backend/IM_API/Interface/Services/ICacheService.cs new file mode 100644 index 0000000..436e606 --- /dev/null +++ b/backend/IM_API/Interface/Services/ICacheService.cs @@ -0,0 +1,49 @@ +using IM_API.Models; + +namespace IM_API.Interface.Services +{ + public interface ICacheService + { + /// + /// 设置缓存 + /// + /// 缓存对象类型 + /// 缓存索引值 + /// 要缓存的对象 + /// 过期时间 + /// + Task SetAsync(string key, T value, TimeSpan? expiration = null); + /// + /// 获取缓存 + /// + /// + /// 缓存索引 + /// + Task GetAsync(string key); + /// + /// 删除缓存 + /// + /// 缓存索引 + /// + Task RemoveAsync(string key); + /// + /// 设置用户缓存 + /// + /// + /// + Task SetUserCacheAsync(User user); + /// + /// 通过用户名获取用户缓存 + /// + /// + /// + Task GetUserCacheAsync(string username); + /// + /// 删除用户缓存 + /// + /// + /// + /// + Task RemoveUserCacheAsync(string username); + } +} diff --git a/backend/IM_API/Interface/Services/IConversationService.cs b/backend/IM_API/Interface/Services/IConversationService.cs index dfc3642..5d24fa5 100644 --- a/backend/IM_API/Interface/Services/IConversationService.cs +++ b/backend/IM_API/Interface/Services/IConversationService.cs @@ -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 /// /// 用户id /// - Task> GetConversationsAsync(int userId); + Task> GetConversationsAsync(int userId); /// /// 获取指定用户的所有推送标识符 /// @@ -34,7 +35,7 @@ namespace IM_API.Interface.Services /// /// /// - Task GetConversationByIdAsync(int userId, int conversationId); + Task GetConversationByIdAsync(int userId, int conversationId); /// /// 清空未读消息 /// @@ -50,5 +51,6 @@ namespace IM_API.Interface.Services /// /// Task MakeConversationAsync(int userAId, int userBId, ChatType chatType); + Task UpdateConversationAfterSentAsync(UpdateConversationDto dto); } } diff --git a/backend/IM_API/Interface/Services/IGroupService.cs b/backend/IM_API/Interface/Services/IGroupService.cs index b337df8..17b2d17 100644 --- a/backend/IM_API/Interface/Services/IGroupService.cs +++ b/backend/IM_API/Interface/Services/IGroupService.cs @@ -11,14 +11,14 @@ namespace IM_API.Interface.Services /// 群ID /// 邀请的用户列表 /// - Task InviteUsers(int userId,int groupId, List userIds); + Task InviteUsersAsync(int userId,int groupId, List userIds); /// /// 加入群聊 /// /// 操作者ID /// 群ID /// - Task JoinGroup(int userId,int groupId); + Task JoinGroupAsync(int userId,int groupId); /// /// 创建群聊 /// @@ -26,14 +26,22 @@ namespace IM_API.Interface.Services /// 群信息 /// 邀请用户列表 /// - Task CreateGroup(int userId, GroupCreateDto groupCreateDto, List userIds); + Task CreateGroupAsync(int userId, GroupCreateDto groupCreateDto); /// /// 删除群 /// /// 操作者ID /// 群ID /// - Task DeleteGroup(int userId, int groupId); - //Task UpdateGroupAuthori + Task DeleteGroupAsync(int userId, int groupId); + /// + /// 获取当前用户群列表 + /// + /// 操作者ID + /// + /// + /// + /// + Task> GetGroupListAsync(int userId, int page, int limit, bool desc); } } diff --git a/backend/IM_API/Interface/Services/IMessageSevice.cs b/backend/IM_API/Interface/Services/IMessageSevice.cs index a7fd68f..3f82049 100644 --- a/backend/IM_API/Interface/Services/IMessageSevice.cs +++ b/backend/IM_API/Interface/Services/IMessageSevice.cs @@ -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 /// 接收人 /// /// - Task SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto); + Task SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto); /// /// 发送群聊消息 /// @@ -19,16 +22,21 @@ namespace IM_API.Interface.Services /// 接收群id /// /// - Task SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto); + Task SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto); /// - /// 获取消息列表 + /// 消息入库 + /// + /// + /// + Task MakeMessageAsync(Message message); + /// + /// 获取历史消息列表 /// /// 会话id(用于获取指定用户间聊天消息) - /// 消息id + /// 消息id /// 获取消息数量 - /// /// - Task> GetMessagesAsync(int userId, int conversationId,int? msgId,int? pageSize,bool desc); + Task> GetMessagesAsync(int userId,MessageQueryDto dto); /// /// 获取未读消息数 /// diff --git a/backend/IM_API/Interface/Services/ISequenceIdService.cs b/backend/IM_API/Interface/Services/ISequenceIdService.cs new file mode 100644 index 0000000..f18fe58 --- /dev/null +++ b/backend/IM_API/Interface/Services/ISequenceIdService.cs @@ -0,0 +1,12 @@ +namespace IM_API.Interface.Services +{ + public interface ISequenceIdService + { + /// + /// 创建消息序号 + /// + /// 聊天唯一标识/param> + /// + Task GetNextSquenceIdAsync(string streamKey); + } +} diff --git a/backend/IM_API/Migrations/20260204140029_InitialCreate.Designer.cs b/backend/IM_API/Migrations/20260204140029_InitialCreate.Designer.cs new file mode 100644 index 0000000..387b874 --- /dev/null +++ b/backend/IM_API/Migrations/20260204140029_InitialCreate.Designer.cs @@ -0,0 +1,1094 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260204140029_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadMessageId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.HasIndex(new[] { "LastReadMessageId" }, "lastMessageId"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Type") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("GroupMemberId") + .HasColumnType("int(11)") + .HasComment("若为群消息则表示具体的成员id"); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", "LastReadMessage") + .WithMany("Conversations") + .HasForeignKey("LastReadMessageId") + .HasConstraintName("conversations_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("LastReadMessage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260204140029_InitialCreate.cs b/backend/IM_API/Migrations/20260204140029_InitialCreate.cs new file mode 100644 index 0000000..2ba062e --- /dev/null +++ b/backend/IM_API/Migrations/20260204140029_InitialCreate.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + 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"); + } + } +} diff --git a/backend/IM_API/Migrations/20260204140708_change_file.Designer.cs b/backend/IM_API/Migrations/20260204140708_change_file.Designer.cs new file mode 100644 index 0000000..608a65f --- /dev/null +++ b/backend/IM_API/Migrations/20260204140708_change_file.Designer.cs @@ -0,0 +1,1094 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260204140708_change_file")] + partial class change_file + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadMessageId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.HasIndex(new[] { "LastReadMessageId" }, "lastMessageId"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("GroupMemberId") + .HasColumnType("int(11)") + .HasComment("若为群消息则表示具体的成员id"); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", "LastReadMessage") + .WithMany("Conversations") + .HasForeignKey("LastReadMessageId") + .HasConstraintName("conversations_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("LastReadMessage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260204140708_change_file.cs b/backend/IM_API/Migrations/20260204140708_change_file.cs new file mode 100644 index 0000000..90bc8f7 --- /dev/null +++ b/backend/IM_API/Migrations/20260204140708_change_file.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class change_file : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Type", + table: "files", + newName: "FileType"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "FileType", + table: "files", + newName: "Type"); + } + } +} diff --git a/backend/IM_API/Migrations/20260204150644_add-SequenceId.Designer.cs b/backend/IM_API/Migrations/20260204150644_add-SequenceId.Designer.cs new file mode 100644 index 0000000..7c6b3e6 --- /dev/null +++ b/backend/IM_API/Migrations/20260204150644_add-SequenceId.Designer.cs @@ -0,0 +1,1093 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260204150644_add-SequenceId")] + partial class addSequenceId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadMessageId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.HasIndex(new[] { "LastReadMessageId" }, "lastMessageId"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", "LastReadMessage") + .WithMany("Conversations") + .HasForeignKey("LastReadMessageId") + .HasConstraintName("conversations_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("LastReadMessage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260204150644_add-SequenceId.cs b/backend/IM_API/Migrations/20260204150644_add-SequenceId.cs new file mode 100644 index 0000000..4940eda --- /dev/null +++ b/backend/IM_API/Migrations/20260204150644_add-SequenceId.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class addSequenceId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GroupMemberId", + table: "messages"); + + migrationBuilder.AddColumn( + name: "SequenceId", + table: "messages", + type: "bigint", + nullable: false, + defaultValue: 0L); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SequenceId", + table: "messages"); + + migrationBuilder.AddColumn( + name: "GroupMemberId", + table: "messages", + type: "int(11)", + nullable: true, + comment: "若为群消息则表示具体的成员id"); + } + } +} diff --git a/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.Designer.cs b/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.Designer.cs new file mode 100644 index 0000000..463cba6 --- /dev/null +++ b/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.Designer.cs @@ -0,0 +1,1094 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260204151306_update-conversation-lastreadmessageid")] + partial class updateconversationlastreadmessageid + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadMessageId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.HasIndex(new[] { "LastReadMessageId" }, "lastMessageId"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", "LastReadMessage") + .WithMany("Conversations") + .HasForeignKey("LastReadMessageId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("conversations_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("LastReadMessage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.cs b/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.cs new file mode 100644 index 0000000..70dc218 --- /dev/null +++ b/backend/IM_API/Migrations/20260204151306_update-conversation-lastreadmessageid.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updateconversationlastreadmessageid : Migration + { + /// + 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); + } + + /// + 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"); + } + } +} diff --git a/backend/IM_API/Migrations/20260205132459_update-message.Designer.cs b/backend/IM_API/Migrations/20260205132459_update-message.Designer.cs new file mode 100644 index 0000000..2b1f3d6 --- /dev/null +++ b/backend/IM_API/Migrations/20260205132459_update-message.Designer.cs @@ -0,0 +1,1101 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260205132459_update-message")] + partial class updatemessage + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadSequenceId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("MessageId") + .HasColumnType("int(11)"); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("MessageId"); + + b.HasIndex(new[] { "LastReadSequenceId" }, "LastReadSequenceId"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("ClientMsgId") + .HasColumnType("char(36)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("SequenceId", "StreamKey") + .IsUnique(); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", null) + .WithMany("Conversations") + .HasForeignKey("MessageId"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260205132459_update-message.cs b/backend/IM_API/Migrations/20260205132459_update-message.cs new file mode 100644 index 0000000..6ff456a --- /dev/null +++ b/backend/IM_API/Migrations/20260205132459_update-message.cs @@ -0,0 +1,93 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updatemessage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "conversations_ibfk_2", + table: "conversations"); + + migrationBuilder.RenameIndex( + name: "lastMessageId", + table: "conversations", + newName: "LastReadSequenceId"); + + migrationBuilder.AddColumn( + name: "ClientMsgId", + table: "messages", + type: "char(36)", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + collation: "ascii_general_ci"); + + migrationBuilder.AddColumn( + 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"); + } + + /// + 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); + } + } +} diff --git a/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.Designer.cs b/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.Designer.cs new file mode 100644 index 0000000..18d1abd --- /dev/null +++ b/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.Designer.cs @@ -0,0 +1,1101 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260207121713_update-datetimeToDateTimeOffset")] + partial class updatedatetimeToDateTimeOffset + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadSequenceId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("MessageId") + .HasColumnType("int(11)"); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("MessageId"); + + b.HasIndex(new[] { "LastReadSequenceId" }, "LastReadSequenceId"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("ClientMsgId") + .HasColumnType("char(36)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("SequenceId", "StreamKey") + .IsUnique(); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", null) + .WithMany("Conversations") + .HasForeignKey("MessageId"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.cs b/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.cs new file mode 100644 index 0000000..15b2ed4 --- /dev/null +++ b/backend/IM_API/Migrations/20260207121713_update-datetimeToDateTimeOffset.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updatedatetimeToDateTimeOffset : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.Designer.cs b/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.Designer.cs new file mode 100644 index 0000000..06934ee --- /dev/null +++ b/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.Designer.cs @@ -0,0 +1,1101 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260207122338_update-DateTimeOffset-type")] + partial class updateDateTimeOffsettype + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadSequenceId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("MessageId") + .HasColumnType("int(11)"); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("MessageId"); + + b.HasIndex(new[] { "LastReadSequenceId" }, "LastReadSequenceId"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("ClientMsgId") + .HasColumnType("char(36)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("SequenceId", "StreamKey") + .IsUnique(); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", null) + .WithMany("Conversations") + .HasForeignKey("MessageId"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.cs b/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.cs new file mode 100644 index 0000000..50750a3 --- /dev/null +++ b/backend/IM_API/Migrations/20260207122338_update-DateTimeOffset-type.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updateDateTimeOffsettype : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/backend/IM_API/Migrations/ImContextModelSnapshot.cs b/backend/IM_API/Migrations/ImContextModelSnapshot.cs new file mode 100644 index 0000000..f0ee0e2 --- /dev/null +++ b/backend/IM_API/Migrations/ImContextModelSnapshot.cs @@ -0,0 +1,1098 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + partial class ImContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadSequenceId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("MessageId") + .HasColumnType("int(11)"); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("MessageId"); + + b.HasIndex(new[] { "LastReadSequenceId" }, "LastReadSequenceId"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("ClientMsgId") + .HasColumnType("char(36)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("SequenceId", "StreamKey") + .IsUnique(); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", null) + .WithMany("Conversations") + .HasForeignKey("MessageId"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Models/Admin.cs b/backend/IM_API/Models/Admin.cs index 2023a98..0da0856 100644 --- a/backend/IM_API/Models/Admin.cs +++ b/backend/IM_API/Models/Admin.cs @@ -30,12 +30,12 @@ public partial class Admin /// /// 创建时间 /// - public DateTime Created { get; set; } + public DateTimeOffset Created { get; set; } /// /// 更新时间 /// - public DateTime Updated { get; set; } + public DateTimeOffset Updated { get; set; } public virtual Role Role { get; set; } = null!; } diff --git a/backend/IM_API/Models/Conversation.cs b/backend/IM_API/Models/Conversation.cs index 1d7b428..eec14a6 100644 --- a/backend/IM_API/Models/Conversation.cs +++ b/backend/IM_API/Models/Conversation.cs @@ -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 /// /// 最后一条未读消息ID /// - public int? LastReadMessageId { get; set; } + public long? LastReadSequenceId { get; set; } /// /// 未读消息数 /// public int UnreadCount { get; set; } - public int ChatType { get; set; } + public ChatType ChatType { get; set; } /// /// 消息推送唯一标识符 @@ -42,9 +43,9 @@ public partial class Conversation /// /// 最后一条消息发送时间 /// - 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!; } diff --git a/backend/IM_API/Models/ConversationExt.cs b/backend/IM_API/Models/ConversationExt.cs new file mode 100644 index 0000000..816e812 --- /dev/null +++ b/backend/IM_API/Models/ConversationExt.cs @@ -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; + } + } + } +} diff --git a/backend/IM_API/Models/Device.cs b/backend/IM_API/Models/Device.cs index a0b6b17..4bfbbb8 100644 --- a/backend/IM_API/Models/Device.cs +++ b/backend/IM_API/Models/Device.cs @@ -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 /// /// 最后一次登录 /// - public DateTime LastLogin { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset LastLogin { get; set; } public virtual User User { get; set; } = null!; } diff --git a/backend/IM_API/Models/File.cs b/backend/IM_API/Models/File.cs index 439b964..5acf177 100644 --- a/backend/IM_API/Models/File.cs +++ b/backend/IM_API/Models/File.cs @@ -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 /// /// 文件类型 /// - public string Type { get; set; } = null!; + public string FileType { get; set; } = null!; /// /// 关联消息ID @@ -35,7 +36,8 @@ public partial class File /// /// 创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual Message Message { get; set; } = null!; } diff --git a/backend/IM_API/Models/Friend.cs b/backend/IM_API/Models/Friend.cs index 11b1221..92fc208 100644 --- a/backend/IM_API/Models/Friend.cs +++ b/backend/IM_API/Models/Friend.cs @@ -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 /// /// 好友关系创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } /// /// 好友备注名 diff --git a/backend/IM_API/Models/Friendrequest.cs b/backend/IM_API/Models/Friendrequest.cs index 2c969b9..410ee9d 100644 --- a/backend/IM_API/Models/Friendrequest.cs +++ b/backend/IM_API/Models/Friendrequest.cs @@ -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 /// /// 申请时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } /// /// 申请附言 diff --git a/backend/IM_API/Models/Group.cs b/backend/IM_API/Models/Group.cs index d58e0aa..dc44d15 100644 --- a/backend/IM_API/Models/Group.cs +++ b/backend/IM_API/Models/Group.cs @@ -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 /// /// 群聊创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } /// /// 群头像 diff --git a/backend/IM_API/Models/Groupinvite.cs b/backend/IM_API/Models/Groupinvite.cs index 3a1e1f5..cca0ad9 100644 --- a/backend/IM_API/Models/Groupinvite.cs +++ b/backend/IM_API/Models/Groupinvite.cs @@ -31,7 +31,7 @@ public partial class GroupInvite /// /// 创建时间 /// - public DateTime? Created { get; set; } + public DateTimeOffset? Created { get; set; } public virtual Group Group { get; set; } = null!; diff --git a/backend/IM_API/Models/Groupmember.cs b/backend/IM_API/Models/Groupmember.cs index e28b91b..f3add73 100644 --- a/backend/IM_API/Models/Groupmember.cs +++ b/backend/IM_API/Models/Groupmember.cs @@ -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 /// /// 加入群聊时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual Group Group { get; set; } = null!; diff --git a/backend/IM_API/Models/Grouprequest.cs b/backend/IM_API/Models/Grouprequest.cs index 660802d..2b774b1 100644 --- a/backend/IM_API/Models/Grouprequest.cs +++ b/backend/IM_API/Models/Grouprequest.cs @@ -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 /// /// 创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual Group Group { get; set; } = null!; diff --git a/backend/IM_API/Models/ImContext.Custom.cs b/backend/IM_API/Models/ImContext.Custom.cs index 93d9546..d02529d 100644 --- a/backend/IM_API/Models/ImContext.Custom.cs +++ b/backend/IM_API/Models/ImContext.Custom.cs @@ -19,7 +19,7 @@ namespace IM_API.Models }); modelBuilder.Entity(entity => { - + entity.Ignore(e => e.ChatTypeEnum); }); modelBuilder.Entity(entity => diff --git a/backend/IM_API/Models/ImContext.cs b/backend/IM_API/Models/ImContext.cs index aeae98b..8304f50 100644 --- a/backend/IM_API/Models/ImContext.cs +++ b/backend/IM_API/Models/ImContext.cs @@ -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)"); diff --git a/backend/IM_API/Models/LoginLog.cs b/backend/IM_API/Models/LoginLog.cs index 2a3d602..1a71b72 100644 --- a/backend/IM_API/Models/LoginLog.cs +++ b/backend/IM_API/Models/LoginLog.cs @@ -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 /// /// 登录时间 /// - public DateTime Logined { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Logined { get; set; } /// /// 登录用户 diff --git a/backend/IM_API/Models/Message.cs b/backend/IM_API/Models/Message.cs index 49d7839..df859ec 100644 --- a/backend/IM_API/Models/Message.cs +++ b/backend/IM_API/Models/Message.cs @@ -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:视频聊天) /// public sbyte MsgType { get; set; } + public Guid ClientMsgId { get; set; } /// /// 消息内容 @@ -42,7 +44,8 @@ public partial class Message /// /// 发送时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } /// /// 消息推送唯一标识符 @@ -50,9 +53,10 @@ public partial class Message public string StreamKey { get; set; } = null!; /// - /// 若为群消息则表示具体的成员id + /// 消息排序标识 /// - public int? GroupMemberId { get; set; } + + public long SequenceId { get; set; } public virtual ICollection Conversations { get; set; } = new List(); diff --git a/backend/IM_API/Models/Notification.cs b/backend/IM_API/Models/Notification.cs index 3273398..4286e14 100644 --- a/backend/IM_API/Models/Notification.cs +++ b/backend/IM_API/Models/Notification.cs @@ -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 /// /// 创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual User User { get; set; } = null!; } diff --git a/backend/IM_API/Models/Permission.cs b/backend/IM_API/Models/Permission.cs index d8ecfc7..9c522c1 100644 --- a/backend/IM_API/Models/Permission.cs +++ b/backend/IM_API/Models/Permission.cs @@ -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 /// /// 创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual ICollection Permissionaroles { get; set; } = new List(); } diff --git a/backend/IM_API/Models/Role.cs b/backend/IM_API/Models/Role.cs index 25490ff..a6a393e 100644 --- a/backend/IM_API/Models/Role.cs +++ b/backend/IM_API/Models/Role.cs @@ -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 /// /// 创建时间 /// - public DateTime Created { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset Created { get; set; } public virtual ICollection Admins { get; set; } = new List(); diff --git a/backend/IM_API/Models/User.cs b/backend/IM_API/Models/User.cs index 0e91c99..3528eda 100644 --- a/backend/IM_API/Models/User.cs +++ b/backend/IM_API/Models/User.cs @@ -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 /// /// 创建时间 /// - public DateTime Created { get; set; } + public DateTimeOffset Created { get; set; } /// /// 修改时间 /// - public DateTime? Updated { get; set; } + [Column(TypeName = "datetimeoffset")] + public DateTimeOffset? Updated { get; set; } /// /// 账户状态 diff --git a/backend/IM_API/Program.cs b/backend/IM_API/Program.cs index 8454049..c006058 100644 --- a/backend/IM_API/Program.cs +++ b/backend/IM_API/Program.cs @@ -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(); //עݿ builder.Services.AddDbContext(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(redis); - builder.Services.AddRabbitMQ(configuration.GetSection("RabbitMqOptions").Get()); + builder.Services.AddStackExchangeRedisCache(options => + { + options.ConnectionMultiplexerFactory = () => Task.FromResult(redis); + }); + + builder.Services.AddRabbitMQ(configuration.GetSection("RabbitMqOptions").Get()); 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()); // 飺շ diff --git a/backend/IM_API/Services/AuthService.cs b/backend/IM_API/Services/AuthService.cs index a387c36..71898c1 100644 --- a/backend/IM_API/Services/AuthService.cs +++ b/backend/IM_API/Services/AuthService.cs @@ -14,15 +14,19 @@ namespace IM_API.Services private readonly ImContext _context; private readonly ILogger _logger; private readonly IMapper _mapper; - public AuthService(ImContext context, ILogger logger, IMapper mapper) + private readonly ICacheService _cache; + public AuthService(ImContext context, ILogger logger, IMapper mapper, ICacheService cache) { _context = context; _logger = logger; _mapper = mapper; + _cache = cache; } public async Task 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; } diff --git a/backend/IM_API/Services/ConversationService.cs b/backend/IM_API/Services/ConversationService.cs index 9d93669..7f18d38 100644 --- a/backend/IM_API/Services/ConversationService.cs +++ b/backend/IM_API/Services/ConversationService.cs @@ -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> GetConversationsAsync(int userId) + public async Task> 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(x.c); + var dto = _mapper.Map(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(x.c); + var dto = _mapper.Map(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 GetConversationByIdAsync(int userId, int conversationId) + public async Task 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(conversation); - //dto.LastReadMessage = _mapper.Map(conversation); - if(conversation.ChatType == (int)ChatType.PRIVATE) + var dto = _mapper.Map(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(); + } } } \ No newline at end of file diff --git a/backend/IM_API/Services/FriendService.cs b/backend/IM_API/Services/FriendService.cs index 692294b..8ed6ba8 100644 --- a/backend/IM_API/Services/FriendService.cs +++ b/backend/IM_API/Services/FriendService.cs @@ -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(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, diff --git a/backend/IM_API/Services/GroupService.cs b/backend/IM_API/Services/GroupService.cs index b05ed05..ff3396b 100644 --- a/backend/IM_API/Services/GroupService.cs +++ b/backend/IM_API/Services/GroupService.cs @@ -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> GetGroupInvites(int userId, int groupId, List 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 CreateGroup(int userId, GroupCreateDto groupCreateDto, List userIds) + public async Task CreateGroupAsync(int userId, GroupCreateDto groupCreateDto) { + List userIds = groupCreateDto.UserIDs ?? []; using var transaction = await _context.Database.BeginTransactionAsync(); try { //先创建群 - DateTime dateTime = DateTime.UtcNow; + DateTime dateTime = DateTime.Now; Group group = _mapper.Map(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 userIds) + public async Task InviteUsersAsync(int userId, int groupId, List 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> 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(_mapper.ConfigurationProvider) + .ToListAsync(); + return list; + } } } diff --git a/backend/IM_API/Services/JWTService.cs b/backend/IM_API/Services/JWTService.cs index f2be501..030beb4 100644 --- a/backend/IM_API/Services/JWTService.cs +++ b/backend/IM_API/Services/JWTService.cs @@ -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()), diff --git a/backend/IM_API/Services/MessageService.cs b/backend/IM_API/Services/MessageService.cs index 0a824c8..73f53b7 100644 --- a/backend/IM_API/Services/MessageService.cs +++ b/backend/IM_API/Services/MessageService.cs @@ -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 logger, IMapper mapper, IEventBus eventBus, - IPublishEndpoint publishEndpoint + ImContext context, ILogger 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> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc) + public async Task> 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(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(); + + return await baseQuery + .Where(m => m.SequenceId > dto.Cursor.Value) + .OrderBy(m => m.SequenceId) // 按时间线正序 + .Take(dto.Limit) + .Select(m => _mapper.Map(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>(msgList); } public Task 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 SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto) + public async Task 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(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(message)); - return _mapper.Map(message); + return _mapper.Map(message); } #endregion #region 发送私聊消息 - public async Task SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto) + public async Task 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(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(message)); - return _mapper.Map(message); + return _mapper.Map(message); } #endregion } diff --git a/backend/IM_API/Services/RedisCacheService.cs b/backend/IM_API/Services/RedisCacheService.cs new file mode 100644 index 0000000..b6bb9a9 --- /dev/null +++ b/backend/IM_API/Services/RedisCacheService.cs @@ -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 GetAsync(string key) + { + var valueBytes= await _cache.GetAsync(key); + if (valueBytes is null || valueBytes.Length == 0) return default; + return JsonSerializer.Deserialize(valueBytes); + } + + public async Task GetUserCacheAsync(string username) + { + var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username); + var userid = await GetAsync(usernameKey); + if (userid is null) return default; + var key = RedisKeys.GetUserinfoKey(userid); + return await GetAsync(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(usernameKey); + if (userid is null) return; + var key = RedisKeys.GetUserinfoKey(userid); + await RemoveAsync(key); + } + + public async Task SetAsync(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()); + } + } +} diff --git a/backend/IM_API/Services/SequenceIdService.cs b/backend/IM_API/Services/SequenceIdService.cs new file mode 100644 index 0000000..3db52a6 --- /dev/null +++ b/backend/IM_API/Services/SequenceIdService.cs @@ -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 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); + } + } +} diff --git a/backend/IM_API/Services/UserService.cs b/backend/IM_API/Services/UserService.cs index dc1e797..dc07845 100644 --- a/backend/IM_API/Services/UserService.cs +++ b/backend/IM_API/Services/UserService.cs @@ -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 _logger; private readonly IMapper _mapper; - public UserService(ImContext imContext,ILogger logger,IMapper mapper) + private readonly ICacheService _cacheService; + public UserService(ImContext imContext,ILogger logger,IMapper mapper, ICacheService cacheService) { this._context = imContext; this._logger = logger; this._mapper = mapper; + _cacheService = cacheService; } #region 获取用户信息 public async Task 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(key); + if (userinfoCache != null) return _mapper.Map(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(user); } #endregion #region 通过用户名获取用户信息 public async Task GetUserInfoByUsernameAsync(string username) { + var userinfo = await _cacheService.GetUserCacheAsync(username); + if (userinfo != null) return _mapper.Map(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(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(user); } #endregion diff --git a/backend/IM_API/Tools/RedisKeys.cs b/backend/IM_API/Tools/RedisKeys.cs new file mode 100644 index 0000000..f1717e9 --- /dev/null +++ b/backend/IM_API/Tools/RedisKeys.cs @@ -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}"; + } + } +} diff --git a/backend/IM_API/Dtos/Auth/LoginDto.cs b/backend/IM_API/VOs/Auth/LoginVo.cs similarity index 61% rename from backend/IM_API/Dtos/Auth/LoginDto.cs rename to backend/IM_API/VOs/Auth/LoginVo.cs index ce9f9dd..3e2c524 100644 --- a/backend/IM_API/Dtos/Auth/LoginDto.cs +++ b/backend/IM_API/VOs/Auth/LoginVo.cs @@ -1,14 +1,14 @@ using IM_API.Dtos.User; -namespace IM_API.Dtos.Auth +namespace IM_API.VOs.Auth { - public class LoginDto + public class LoginVo { public UserInfoDto userInfo { get; set; } public string Token { get; set; } public string RefreshToken { get; set; } - public DateTime ExpireAt { get; set; } - public LoginDto(UserInfoDto userInfo,string token, string refreshToken, DateTime expireAt) { + public DateTimeOffset ExpireAt { get; set; } + public LoginVo(UserInfoDto userInfo,string token, string refreshToken, DateTime expireAt) { this.userInfo = userInfo; Token = token; RefreshToken = refreshToken; diff --git a/backend/IM_API/Dtos/Conversation/ConversationDto.cs b/backend/IM_API/VOs/Conversation/ConversationVo.cs similarity index 76% rename from backend/IM_API/Dtos/Conversation/ConversationDto.cs rename to backend/IM_API/VOs/Conversation/ConversationVo.cs index 0efcf36..5d98a82 100644 --- a/backend/IM_API/Dtos/Conversation/ConversationDto.cs +++ b/backend/IM_API/VOs/Conversation/ConversationVo.cs @@ -1,8 +1,10 @@ -using IM_API.Models; +using IM_API.Dtos; +using IM_API.Models; +using IM_API.VOs.Message; -namespace IM_API.Dtos.Conversation +namespace IM_API.VOs.Conversation { - public class ConversationDto + public class ConversationVo { public int Id { get; set; } @@ -19,14 +21,14 @@ namespace IM_API.Dtos.Conversation /// /// 最后一条未读消息ID /// - public int? LastReadMessageId { get; set; } + public long? LastSequenceId { get; set; } /// /// 未读消息数 /// public int UnreadCount { get; set; } - public int ChatType { get; set; } + public ChatType ChatType { get; set; } /// @@ -36,7 +38,7 @@ namespace IM_API.Dtos.Conversation /// /// 最后一条消息时间 /// - public DateTime? DateTime { get; set; } = null; + public DateTimeOffset? DateTime { get; set; } = null; /// /// 对方昵称 @@ -46,6 +48,6 @@ namespace IM_API.Dtos.Conversation /// 对方头像 /// public string? TargetAvatar { get; set; } - public MessageBaseDto? LastReadMessage { get; set; } + } } diff --git a/backend/IM_API/VOs/Message/MessageBaseVo.cs b/backend/IM_API/VOs/Message/MessageBaseVo.cs new file mode 100644 index 0000000..57ccb78 --- /dev/null +++ b/backend/IM_API/VOs/Message/MessageBaseVo.cs @@ -0,0 +1,9 @@ +using IM_API.Dtos; + +namespace IM_API.VOs.Message +{ + public record MessageBaseVo:MessageBaseDto + { + public int SequenceId { get; set; } + } +} diff --git a/frontend/web/.env b/frontend/web/.env index d3d8fa0..d91c4c6 100644 --- a/frontend/web/.env +++ b/frontend/web/.env @@ -1,4 +1,4 @@ -#VITE_API_BASE_URL = http://localhost:5202/api -#VITE_SIGNALR_BASE_URL = http://localhost:5202/chat/ -VITE_API_BASE_URL = https://im.test.nxsir.cn/api -VITE_SIGNALR_BASE_URL = https://im.test.nxsir.cn/chat/ \ No newline at end of file +VITE_API_BASE_URL = http://localhost:5202/api +VITE_SIGNALR_BASE_URL = http://localhost:5202/chat/ +# VITE_API_BASE_URL = https://im.test.nxsir.cn/api +# VITE_SIGNALR_BASE_URL = https://im.test.nxsir.cn/chat/ \ No newline at end of file diff --git a/frontend/web/src/components/messages/HistoryLoading.vue b/frontend/web/src/components/messages/HistoryLoading.vue new file mode 100644 index 0000000..d79ab8a --- /dev/null +++ b/frontend/web/src/components/messages/HistoryLoading.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/frontend/web/src/constants/MessageType.js b/frontend/web/src/constants/MessageType.js new file mode 100644 index 0000000..5d2043d --- /dev/null +++ b/frontend/web/src/constants/MessageType.js @@ -0,0 +1,4 @@ +export const MESSAGE_TYPE = Object.freeze({ + PRIVATE: 'PRIVATE', + GROUP: 'GROUP' +}) \ No newline at end of file diff --git a/frontend/web/src/services/message.js b/frontend/web/src/services/message.js index 9949563..213fdca 100644 --- a/frontend/web/src/services/message.js +++ b/frontend/web/src/services/message.js @@ -25,11 +25,18 @@ export const messageService = { * @param {*} pageSize * @returns */ - getHistoryMessages: (conversationId, msgId, pageSize = 10) => request.get(`/message/getmessageList?conversationId=${conversationId}&msgId=${msgId}&pageSize=${pageSize}`), + //getHistoryMessages: (conversationId, msgId, pageSize = 10) => request.get(`/message/getmessageList?conversationId=${conversationId}&msgId=${msgId}&pageSize=${pageSize}`), /** - * 获取最新消息 - * @param {*} conversationId + * 获取消息 + * @param {*} conversationId 会话ID + * @param {Number} cursor 锚点(对应sequenceId),查询最新消息传null + * @param {Number} direction 方向 0为查历史消息 1为查锚点后的消息,查询最新消息传0 需配合cursor + * @param {number} limit 单次查询消息数 * @returns */ - getMessages: (conversationId) => request.get(`/message/getmessageList?conversationId=${conversationId}`) + getMessages: (conversationId, cursor, direction, limit) => request.get( + `/message/getmessageList?conversationId=${conversationId}${cursor ? '&cursor=' + cursor : ''}&direction=${direction}&limit=${limit}` + ), + + sendMessage: (msg) => request.post('/Message/SendMessage', msg) } \ No newline at end of file diff --git a/frontend/web/src/stores/chat.js b/frontend/web/src/stores/chat.js index c061e4f..8cd9039 100644 --- a/frontend/web/src/stores/chat.js +++ b/frontend/web/src/stores/chat.js @@ -1,30 +1,41 @@ import { defineStore } from "pinia"; import { messagesDb } from "@/utils/db/messageDB"; import { messageService } from "@/services/message"; +import { useConversationStore } from "./conversation"; export const useChatStore = defineStore('chat', { state: () => ({ - activeSessionId: null, activeConversationId: null, + activeSessionId: null, + maxSequenceId: null, + isEnded: false, messages: [], pageSize: 20 }), actions: { // 抽取统一的排序去重方法 - pushAndSortMessages(newMsgs) { - const combined = [...this.messages, ...newMsgs]; - // 1. 根据 msgId 或唯一 key 去重 - const uniqueMap = new Map(); - combined.forEach(m => uniqueMap.set(m.msgId || m.id, m)); + async pushAndSortMessagesAsync(newMsgs, sessionId, shouldSaveToDb = true) { + if (shouldSaveToDb) { + for (const m of newMsgs) { + await messagesDb.save({ ...m, sessionId }); + } + } - // 2. 转换为数组并按时间戳升序排序 (旧的在前,新的在后) - this.messages = Array.from(uniqueMap.values()).sort((a, b) => { - return new Date(a.timeStamp).getTime() - new Date(b.timeStamp).getTime(); - }); - }, - async addMessage(msg, sessionId) { - await messagesDb.save({ ...msg, sessionId, isLoading: false }); - this.messages.push({ ...msg, sessionId }) + if (sessionId == this.activeSessionId) { + const combined = [...this.messages, ...newMsgs]; + // 1. 根据 msgId 或唯一 key 去重 + const uniqueMap = new Map(); + combined.forEach(m => uniqueMap.set(m.msgId || m.sequenceId, m)); + + // 2. 转换为数组并按sequenceId升序排序 (旧的在前,新的在后) + this.messages = Array.from(uniqueMap.values()).sort((a, b) => { + return a.sequenceId - b.sequenceId; + }); + this.maxSequenceId = this.messages.reduce((max, m) => + m.sequenceId > max ? m.sequenceId : max, + null // 初始值 + ); + } }, /** * 切换会话加载当前会话消息列表 @@ -34,38 +45,26 @@ export const useChatStore = defineStore('chat', { this.activeSessionId = sessionId; this.activeConversationId = conversationId; this.messages = []; + this.isEnded = false; //先从浏览器缓存加载一部分消息列表 - const localHistory = await messagesDb.getPageMessages(sessionId, new Date().toISOString(), this.pageSize); + const localHistory = await messagesDb.getLatestMessages(sessionId, this.pageSize); console.log(localHistory) if (localHistory.length > 0) { this.messages = localHistory; - } else { - //如果本地没有消息数据则从后端拉取数据 - const conversation = (await messageService.getConversationById(this.activeConversationId)).data; - const serverHistoryMsg = await this.fetchHistoryFromServer(this.activeConversationId, conversation.lastReadMessageId); - //对消息进行过滤,防止重复消息 - const filterMsg = serverHistoryMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId)); - this.pushAndSortMessages([...filterMsg, ...this.messages]); + this.maxSequenceId = this.messages.reduce((max, m) => + m.sequenceId > max ? m.sequenceId : max, + null // 初始值 + ); } - //拉取新消息 - this.fetchNewMsgFromServier(this.activeConversationId).then((newMsg) => { - //去重 - const filterNewMsg = newMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId)); - this.pushAndSortMessages([...filterNewMsg, ...this.messages]) - }); }, /** * 从服务器加载新消息 * @param {*} sessionId * @returns */ - async fetchNewMsgFromServier(conversationId) { - const newMsg = (await messageService.getMessages(conversationId)).data; + async fetchNewMsgFromServier(conversationId, sequenceId) { + const newMsg = (await messageService.getMessages(conversationId, sequenceId, sequenceId ? 1 : 0, this.pageSize)).data; if (newMsg.length > 0) { - const sessionId = this.activeSessionId; - await Promise.all(newMsg.map(msg => - messagesDb.save({ ...msg, sessionId }) - )); return newMsg; } else { return []; @@ -77,14 +76,11 @@ export const useChatStore = defineStore('chat', { * @param {*} msgId * @returns */ - async fetchHistoryFromServer(conversationId, msgId) { - const res = (await messageService.getHistoryMessages(conversationId, msgId, this.pageSize)).data; + async fetchHistoryFromServer(conversationId, sequenceId) { + const res = (await messageService.getMessages(conversationId, sequenceId, 0, this.pageSize)).data; if (res.length > 0) { const sessionId = this.activeSessionId; - await Promise.all(res.map(msg => - messagesDb.save({ ...msg, sessionId }) - )); return res; } else { return []; @@ -94,14 +90,28 @@ export const useChatStore = defineStore('chat', { * 加载更多历史消息 */ async loadMoreMessages() { - const lastTimeStamp = this.messages.length > 0 ? this.messages[0].timeStamp : new Date().toISOString(); - const history = await messagesDb.getPageMessages(this.activeSessionId, lastTimeStamp, this.pageSize); - if (history.length > 0) { - this.messages = [...history, ...this.messages] - } else { - const fetchMsg = await this.fetchHistoryFromServer(this.conversationId, this.messages[0].msgId); - const newMsgs = fetchMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId)); - this.pushAndSortMessages([...newMsgs, ...this.messages]) + let minSequenceId = 0; + if (!this.messages || this.messages.length === 0) return; + minSequenceId = this.messages.reduce((min, m) => + (m.sequenceId < min ? m.sequenceId : min), + this.messages[0].sequenceId // 使用第一项作为初始参考值 + ); + const dbCacheList = await messagesDb.getPageMessages(this.activeSessionId, minSequenceId, this.pageSize) + const dbMaxSequenceId = dbCacheList.reduce((max, m) => + m.sequenceId > max ? m.sequenceId : max, + null // 初始值 + ); + if (dbCacheList.length < this.pageSize) { + const newList = await this.fetchHistoryFromServer(this.activeConversationId, minSequenceId) + if (newList.length === 0) this.isEnded = true; + await this.pushAndSortMessagesAsync(newList, this.activeSessionId, true); + } else if (dbMaxSequenceId < minSequenceId - 1) { + const newList = await this.fetchHistoryFromServer(this.activeConversationId, minSequenceId) + if (newList.length === 0) this.isEnded = true; + await this.pushAndSortMessagesAsync(newList, this.activeSessionId, true); + } + else { + await this.pushAndSortMessagesAsync(dbCacheList, this.activeSessionId, false); } } } diff --git a/frontend/web/src/stores/conversation.js b/frontend/web/src/stores/conversation.js index f97ec90..66e2031 100644 --- a/frontend/web/src/stores/conversation.js +++ b/frontend/web/src/stores/conversation.js @@ -31,7 +31,7 @@ export const useConversationStore = defineStore('conversation', { try { const covnersationsCache = await conversationDb.getAll(); if (covnersationsCache && covnersationsCache.length > 0) { - covnersationsCache.sort((a, b) => { + this.conversations = covnersationsCache.sort((a, b) => { return new Date(a.dateTime) - new Date(b.dateTime); }) } @@ -40,7 +40,7 @@ export const useConversationStore = defineStore('conversation', { console.log('读取本地会话缓存失败:', e); } } - await this.fetchConversationsFromServier() + //await this.fetchConversationsFromServier() }, /** * 从服务器加载新消息 diff --git a/frontend/web/src/stores/signalr.js b/frontend/web/src/stores/signalr.js index 8570905..3b4f66d 100644 --- a/frontend/web/src/stores/signalr.js +++ b/frontend/web/src/stores/signalr.js @@ -8,6 +8,8 @@ import { generateSessionId } from "@/utils/sessionIdTools"; import { messageHandler } from "@/handler/messageHandler"; import { useBrowserNotification } from "@/services/useBrowserNotification"; import { useConversationStore } from "./conversation"; +import { SignalRMessageHandler } from "@/utils/signalr/SignalMessageHandler"; +import { signalRConnectionEventHandler } from "@/utils/signalr/signalRConnectionEventHandler"; export const useSignalRStore = defineStore('signalr', { state: () => ({ @@ -21,44 +23,43 @@ export const useSignalRStore = defineStore('signalr', { const url = import.meta.env.VITE_SIGNALR_BASE_URL || 'http://localhost:5202/chat/'; this.connection = new signalR.HubConnectionBuilder() .withUrl(url, - { + { - accessTokenFactory: async () => { - if (authStore.isTokenExpired) { - const res = await authService.refresh(authStore.refreshToken) - authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo) + accessTokenFactory: async () => { + if (authStore.isTokenExpired) { + const res = await authService.refresh(authStore.refreshToken) + authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo) + } + return authStore.token; } - return authStore.token; - } - }) + }) .withAutomaticReconnect() .build(); this.registerHandlers(); try { await this.connection.start(); this.isConnected = true; + signalRConnectionEventHandler(); console.log('SignalR建立通信成功!') } catch (e) { message.error('与服务器建立通信失败,请检查网络连接...'); } }, registerHandlers() { - const chatStore = useChatStore() - const browserNotification = useBrowserNotification(); + this.connection.on('ReceiveMessage', (msg) => { - const sessionId = generateSessionId(msg.senderId, msg.receiverId); - messageHandler(msg); - chatStore.addMessage(msg, sessionId); - const conversation = useConversationStore().conversations.find(x => x.targetId == msg.senderId); - browserNotification.send(`${conversation.targetName}发来一条消息`, { - body: msg.content, - icon: conversation.targetAvatar - }); + console.log(msg) + SignalRMessageHandler(msg) }); - this.connection.onclose(() => { this.isConnected = false }); - this.connection.onreconnected(() => { this.isConnected = true }); + this.connection.onclose(() => { + this.isConnected = false; + }); + this.connection.onreconnected(() => { + this.isConnected = true; + signalRConnectionEventHandler(); + }); }, /** diff --git a/frontend/web/src/utils/dateTool.js b/frontend/web/src/utils/dateTool.js new file mode 100644 index 0000000..63127fa --- /dev/null +++ b/frontend/web/src/utils/dateTool.js @@ -0,0 +1,13 @@ +export const GetLocalIso = (date) => { + // 考虑到时区偏差,手动构造符合 C# 要求的本地 ISO 字符串 + const offset = -date.getTimezoneOffset(); + const diff = offset >= 0 ? '+' : '-'; + const pad = (num) => String(num).padStart(2, '0'); + + return date.getFullYear() + + '-' + pad(date.getMonth() + 1) + + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + + ':' + pad(date.getMinutes()) + + ':' + pad(date.getSeconds()); +} \ No newline at end of file diff --git a/frontend/web/src/utils/db/baseDb.js b/frontend/web/src/utils/db/baseDb.js index 493184c..e58f510 100644 --- a/frontend/web/src/utils/db/baseDb.js +++ b/frontend/web/src/utils/db/baseDb.js @@ -5,13 +5,15 @@ const STORE_NAME = 'messages'; const CONVERSARION_STORE_NAME = 'conversations'; const CONTACT_STORE_NAME = 'contacts'; -export const dbPromise = openDB(DBNAME, 5, { +export const dbPromise = openDB(DBNAME, 7, { upgrade(db) { if (!db.objectStoreNames.contains(STORE_NAME)) { const store = db.createObjectStore(STORE_NAME, { keyPath: 'msgId' }); store.createIndex('by-sessionId', 'sessionId'); store.createIndex('by-time', 'timeStamp'); - store.createIndex('by-session-time', ['sessionId', 'timeStamp']); + store.createIndex('by-sequenceId', 'sequenceId'); + store.createIndex('by-session-sequenceId', ['sessionId', 'sequenceId']); + } if (!db.objectStoreNames.contains(CONVERSARION_STORE_NAME)) { const store = db.createObjectStore(CONVERSARION_STORE_NAME, { keyPath: 'id' }); diff --git a/frontend/web/src/utils/db/messageDB.js b/frontend/web/src/utils/db/messageDB.js index cde60bc..07d81bd 100644 --- a/frontend/web/src/utils/db/messageDB.js +++ b/frontend/web/src/utils/db/messageDB.js @@ -12,15 +12,15 @@ export const messagesDb = { async clearAll() { return (await dbPromise).clear(STORE_NAME); }, - async getPageMessages(sessionId, beforeTimeStamp, limit = 20) { + async getPageMessages(sessionId, beforeSequenceId, limit = 20) { const db = await dbPromise; const tx = db.transaction(STORE_NAME, 'readonly'); - const index = tx.store.index('by-session-time'); // 使用复合索引 + const index = tx.store.index('by-session-sequenceId'); // 使用复合索引 // 定义范围:从 [sessionId, 最早时间] 到 [sessionId, beforeTimeStamp) // 注意:IDBKeyRange.bound([sessionId, ""], [sessionId, beforeTimeStamp], false, true) // 或者简单使用 upperbound 限制最大值 - const range = IDBKeyRange.upperBound([sessionId, beforeTimeStamp], true); + const range = IDBKeyRange.upperBound([sessionId, beforeSequenceId], true); // 'prev' 表示从最新的往回找(倒序) let cursor = await index.openCursor(range, 'prev'); @@ -36,5 +36,29 @@ export const messagesDb = { } return results; + }, + async getLatestMessages(sessionId, limit) { + const db = await dbPromise; + const tx = db.transaction(STORE_NAME, 'readonly'); + const index = tx.store.index('by-session-sequenceId'); + + // 关键点:范围只限定 sessionId,不限 sequenceId 的上限 + // 复合索引中,[sessionId, []] 到 [sessionId, [Infinity]] 会覆盖该 session 下所有数据 + const range = IDBKeyRange.bound([sessionId, 0], [sessionId, Infinity]); + + // 使用 'prev' 游标,从最大的 sequenceId 开始往前找 + let cursor = await index.openCursor(range, 'prev'); + const results = []; + + while (cursor && results.length < limit) { + // 虽然有 bound 约束,但为了防御性编程,依然建议检查 sessionId + if (cursor.value.sessionId !== sessionId) break; + + results.push(cursor.value); + cursor = await cursor.continue(); + } + + // 因为是倒序捞出来的(20, 19, 18...),最后要反转一下变成升序给界面渲染 + return results.reverse(); } } \ No newline at end of file diff --git a/frontend/web/src/utils/sessionIdTools.js b/frontend/web/src/utils/sessionIdTools.js index 95d2b1d..57b9fde 100644 --- a/frontend/web/src/utils/sessionIdTools.js +++ b/frontend/web/src/utils/sessionIdTools.js @@ -3,9 +3,12 @@ * @param {string|number} id1 用户A的ID * @param {string|number} id2 用户B的ID */ -export const generateSessionId = (id1, id2) => { +export const generateSessionId = (id1, id2, isGroup = false) => { // 1. 转换为字符串并放入数组 // 2. 排序(确保顺序一致性) // 3. 用下划线或其他分隔符拼接 + if (isGroup) { + return `g:${id2}`; + } return [String(id1), String(id2)].sort().join('_'); }; \ No newline at end of file diff --git a/frontend/web/src/utils/signalr/SignalMessageHandler.js b/frontend/web/src/utils/signalr/SignalMessageHandler.js new file mode 100644 index 0000000..15ca6f7 --- /dev/null +++ b/frontend/web/src/utils/signalr/SignalMessageHandler.js @@ -0,0 +1,21 @@ +import { useBrowserNotification } from "@/services/useBrowserNotification"; + +import { useChatStore } from "@/stores/chat"; + +import { messageHandler } from "@/handler/messageHandler"; +import { generateSessionId } from "../sessionIdTools"; +import { useConversationStore } from "@/stores/conversation"; + +export const SignalRMessageHandler = (data) => { + const msg = data.data; + const chatStore = useChatStore() + const browserNotification = useBrowserNotification(); + const sessionId = generateSessionId(msg.senderId, msg.receiverId); + messageHandler(msg); + chatStore.pushAndSortMessagesAsync([msg], sessionId); + const conversation = useConversationStore().conversations.find(x => x.targetId == msg.senderId); + browserNotification.send(`${conversation.targetName}发来一条消息`, { + body: msg.content, + icon: conversation.targetAvatar + }); +} \ No newline at end of file diff --git a/frontend/web/src/utils/signalr/signalRConnectionEventHandler.js b/frontend/web/src/utils/signalr/signalRConnectionEventHandler.js new file mode 100644 index 0000000..3ad1354 --- /dev/null +++ b/frontend/web/src/utils/signalr/signalRConnectionEventHandler.js @@ -0,0 +1,10 @@ +import { useConversationStore } from "@/stores/conversation" + +export const signalRConnectionEventHandler = () => { + const conversationStore = useConversationStore(); + conversationStore.fetchConversationsFromServier().then(res => { + conversationStore.conversations.forEach(element => { + element.isInitialized = false; + }); + }) +} \ No newline at end of file diff --git a/frontend/web/src/views/messages/MessageContent.vue b/frontend/web/src/views/messages/MessageContent.vue index d1632a5..ecdc5ab 100644 --- a/frontend/web/src/views/messages/MessageContent.vue +++ b/frontend/web/src/views/messages/MessageContent.vue @@ -9,15 +9,16 @@
+
- +
{{ m.content }}
{{ m.content }}
- +
@@ -49,7 +50,7 @@ diff --git a/frontend/web/src/views/messages/MessageList.vue b/frontend/web/src/views/messages/MessageList.vue index 11347a5..ec6b4d3 100644 --- a/frontend/web/src/views/messages/MessageList.vue +++ b/frontend/web/src/views/messages/MessageList.vue @@ -19,7 +19,7 @@
- + {{ s.unreadCount ?? 0 }}