72 Commits
Author SHA1 Message Date
nanxun f6ff17ed02 Merge pull request 'main' (#54) from main into y1
Reviewed-on: #54
2026-01-30 00:45:07 +08:00
nanxun c265868a51 Merge pull request '前端:修复wss连接失败的问题' (#53) from feature-nxdev into main
Reviewed-on: #53
2026-01-29 23:44:46 +08:00
nanxun 7ebf23ddd8 前端:修复wss连接失败的问题 2026-01-29 23:43:50 +08:00
nanxun 8938dd40d4 Merge pull request '前端:修复多分支部署时静态资源的路径问题' (#52) from feature-nxdev into main
Reviewed-on: #52
2026-01-29 22:54:48 +08:00
nanxun a590aa0a26 前端:修复多分支部署时静态资源的路径问题 2026-01-29 22:54:12 +08:00
nanxun 98b5cad586 Merge pull request 'feature-nxdev' (#51) from feature-nxdev into main
Reviewed-on: #51
2026-01-29 22:31:28 +08:00
nanxun 3d9335b97b 前端:修复多分支部署时静态资源的路径问题 2026-01-29 22:30:35 +08:00
nanxun 1df3a0f30e 1111 2026-01-28 18:27:13 +08:00
nanxun e65eeb5ff7 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-28 18:27:08 +08:00
nanxun e05497a478 1111 2026-01-28 18:26:19 +08:00
nanxun ae99d3290f 后端:
1、事件订阅增加rabbitmq中间件
移动端:
1、完善移动端目录结构和代码框架
2026-01-28 18:19:45 +08:00
nanxun 6cab42679c Merge pull request '前端:修复一堆BUG' (#50) from feature-nxdev into main
Reviewed-on: #50
2026-01-24 14:39:52 +08:00
nanxun f9cc69698c Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM 2026-01-24 14:37:53 +08:00
nanxun f41e7137fe 前端:修复一堆BUG 2026-01-23 17:22:07 +08:00
nanxun 9d942b38c7 Merge pull request 'feature-nxdev' (#49) from feature-nxdev into main
Reviewed-on: #49
2026-01-22 15:29:17 +08:00
nanxun 435db45a52 更新 2026-01-22 15:28:42 +08:00
nanxun a31735dbe2 前端:
修复了已知问题
后端:
修复了已知问题
2026-01-22 15:25:54 +08:00
nanxun 11840a2f30 Merge pull request '前端:' (#48) from feature-nxdev into main
Reviewed-on: #48
2026-01-21 22:32:37 +08:00
nanxun f7e68555b1 前端:
新增添加好友功能
修复给联系人发消息时跳转空会话的情况
修复了已知问题
后端:
修复了已知问题
2026-01-21 22:30:51 +08:00
nanxun cca84f88c0 Merge pull request '前端:' (#47) from feature-nxdev into main
Reviewed-on: #47
2026-01-21 18:50:42 +08:00
nanxun 77744015de 前端:
1、更新界面图标
2、修改消息缓存逻辑
3、新增清空未读消息数
后端:
1、修复不同时区导致的时间显示问题
2、新增清空未读消息数接口
2026-01-21 18:48:05 +08:00
nanxun e18fb9db48 Merge pull request '前端:' (#46) from feature-nxdev into main
Reviewed-on: #46
2026-01-20 23:11:48 +08:00
nanxun bedcf97c9d 前端:
增加会话缓存
后端:
增加触发消息创建事件
增加消息创建事件触发后的处理函数
2026-01-20 23:09:46 +08:00
nanxun df044e23f1 Merge pull request '前端&后端:' (#45) from feature-nxdev into main
Reviewed-on: #45
2026-01-20 20:26:34 +08:00
nanxun be621e9ae2 前端&后端:
互发消息流程打通
2026-01-20 20:25:08 +08:00
nanxun 204853beb9 Merge pull request '前端:' (#44) from feature-nxdev into main
Reviewed-on: #44
2026-01-19 19:39:00 +08:00
nanxun 507788f6a3 前端:
引用signalr三方包
2026-01-19 19:37:47 +08:00
nanxun ac54af3ff8 Merge pull request '前端:' (#43) from feature-nxdev into main
Reviewed-on: #43
2026-01-18 22:38:39 +08:00
nanxun e7dbb651a2 前端:
1、会话列表、消息界面展示与后端打通
后端:
1、修复会话和消息服务现存问题
2、会话对象不再返回Message对象,而是使用MessageBaseDto替代
3、修改查询会话列表和会话信息的逻辑
4、新增消息列表查询
文档:
后端代码规范文档新增从数据库同步到模型的命令
2026-01-18 22:32:55 +08:00
nanxun f565e9d8a3 Merge pull request 'feature-nxdev' (#42) from feature-nxdev into main
Reviewed-on: #42
2026-01-13 22:08:35 +08:00
nanxun d855c8f8fb 前端:完善无感刷新
后端:修复刷新token接口异常报错
2026-01-13 22:07:00 +08:00
nanxun 3b5e8b8eba Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 21:01:21 +08:00
nanxun 3b126f17fc Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 21:01:13 +08:00
nanxun 448cf25538 Merge pull request 'feature-nxdev' (#41) from feature-nxdev into main
Reviewed-on: #41
2026-01-13 20:15:59 +08:00
nanxun 2b2d018658 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 20:14:31 +08:00
nanxun 816b01f13a Merge pull request 'main' (#40) from main into feature-nxdev
Reviewed-on: #40
2026-01-13 20:14:27 +08:00
nanxun da401a20c0 Merge pull request 'main' (#40) from main into feature-nxdev
Reviewed-on: #40
2026-01-13 19:56:57 +08:00
nanxun 9e826153a4 Merge pull request '修改侧栏,添加联系人跳转消息' (#39) from y1 into main
Reviewed-on: #39
2026-01-12 17:40:56 +08:00
chuyang ca4e2b7dd5 修改侧栏,添加联系人跳转消息 2026-01-12 16:44:32 +08:00
nanxun 57deb5c9bf Merge pull request 'feature-nxdev' (#38) from feature-nxdev into main
Reviewed-on: #38
2026-01-03 22:14:44 +08:00
nanxun 5323c29549 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-03 22:13:38 +08:00
nanxun 41963dde70 add(conversation):完善私聊好友会话服务 2026-01-03 22:13:28 +08:00
nanxun 7d5699ad53 Merge pull request 'feature-nxdev' (#37) from feature-nxdev into main
Reviewed-on: #37
2025-12-30 19:19:35 +08:00
nanxun 5274f0d22d Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 19:19:08 +08:00
nanxun e981ee190f Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 19:18:58 +08:00
nanxun 85144dbf6f Merge pull request 'feature-nxdev' (#36) from feature-nxdev into main
Reviewed-on: #36
2025-12-30 17:59:50 +08:00
nanxun f42e990c45 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:59:02 +08:00
nanxun 705c96a230 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:58:59 +08:00
nanxun 885b4012ed Merge pull request 'feature-nxdev' (#35) from feature-nxdev into main
Reviewed-on: #35
2025-12-30 17:22:51 +08:00
nanxun 2565678b22 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:21:20 +08:00
nanxun f89007f52b Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:21:11 +08:00
nanxun 36f442a1c2 Merge pull request 'feature-nxdev' (#34) from feature-nxdev into main
Reviewed-on: #34
2025-12-29 22:03:13 +08:00
nanxun 0ea560694b Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 22:02:58 +08:00
nanxun c764f0eb03 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 22:02:55 +08:00
nanxun 600272486b Merge pull request 'feature-nxdev' (#33) from feature-nxdev into main
Reviewed-on: #33
2025-12-29 21:57:59 +08:00
nanxun 0a440d2914 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 21:57:40 +08:00
nanxun d641cb3275 Merge pull request 'main' (#32) from main into feature-nxdev
Reviewed-on: #32
2025-12-29 21:57:33 +08:00
nanxun 26a13c4165 Merge pull request 'main' (#32) from main into feature-nxdev
Reviewed-on: #32
2025-12-29 20:46:10 +08:00
nanxun 6dd93529c2 Merge pull request 'feat/auth-ui-refinement' (#31) from feat/auth-ui-refinement into main
Reviewed-on: #31
2025-12-29 20:45:30 +08:00
nanxun 86841867f3 Merge pull request 'feature-nxdev' (#30) from feature-nxdev into main
Reviewed-on: #30
2025-12-29 20:41:46 +08:00
nanxun 191c05461a Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 20:41:19 +08:00
nanxun cf09984056 add(conversationSerivice):后端新增消息会话服务add(main):完善主面板 2025-12-29 20:39:29 +08:00
4yanzhen a40b768f3b style: update App.vue and IconInput.vue for consistent auth UI 2025-12-29 20:37:14 +08:00
4yanzhen ad0ab87747 style: refine login and register pages with green/blue themes and pill-shaped buttons 2025-12-29 20:32:40 +08:00
nanxun afe2c38f74 Merge pull request 'add(main):完善主面板' (#29) from feature-nxdev into main
Reviewed-on: #29
2025-12-29 16:16:03 +08:00
nanxun 964b129e5e add(main):完善主面板 2025-12-29 16:14:50 +08:00
nanxun 4cc1d20b7f Merge pull request 'feature-nxdev' (#28) from feature-nxdev into main
Reviewed-on: #28
2025-12-28 22:14:18 +08:00
nanxun 06410a75e7 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:13:39 +08:00
nanxun 6c564ceba3 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:13:35 +08:00
nanxun 9c108de175 Merge pull request 'feature-nxdev' (#27) from feature-nxdev into main
Reviewed-on: #27
2025-12-28 22:02:09 +08:00
nanxun ceae1a0cd8 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:01:27 +08:00
nanxun 62642ee86a modify(login): 修改登录返回对象,新增返回用户信息 2025-12-28 22:01:24 +08:00
200 changed files with 8205 additions and 893 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@
"Key": "YourSuperSecretKey123456784124214190!",
"Issuer": "IMDemo",
"Audience": "IMClients",
"AccessTokenMinutes": 15,
"AccessTokenMinutes": 30,
"RefreshTokenDays": 30
},
"ConnectionStrings": {
@@ -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+d852258d4fa2cf8bb6e4645d23894dfb8d8e4b1f")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1df3a0f30e469671b3e81d88574871d99c98af1a")]
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
db83b7267fcf05069f219fdc3eec31e2a4e8928c3bd33341b22a7f66599bc599
9546071857f1b0fa09bedf887ca476b7ecfa579752230651321c2a145e579c92
@@ -1,5 +1,7 @@
is_global = true
build_property.TargetFramework = net8.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v8.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
@@ -1,9 +1,9 @@
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
global using global::Xunit;
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;
global using Xunit;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,7 +49,7 @@
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net8.0": {
@@ -96,7 +96,7 @@
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.304/PortableRuntimeIdentifierGraph.json"
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
}
}
},
@@ -141,7 +141,7 @@
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net8.0": {
@@ -155,6 +155,10 @@
"target": "Package",
"version": "[12.0.0, )"
},
"MassTransit.RabbitMQ": {
"target": "Package",
"version": "[8.5.5, )"
},
"Microsoft.AspNetCore.Authentication.JwtBearer": {
"target": "Package",
"version": "[8.0.21, )"
@@ -219,7 +223,7 @@
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.304/PortableRuntimeIdentifierGraph.json"
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
}
}
}
@@ -7,7 +7,7 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\nanxun\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.1</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\nanxun\.nuget\packages\" />
+275 -3
View File
@@ -53,6 +53,57 @@
"build/netstandard1.0/coverlet.collector.targets": {}
}
},
"MassTransit/8.5.5": {
"type": "package",
"dependencies": {
"MassTransit.Abstractions": "8.5.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.0",
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
},
"compile": {
"lib/net8.0/MassTransit.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/MassTransit.dll": {
"related": ".xml"
}
}
},
"MassTransit.Abstractions/8.5.5": {
"type": "package",
"compile": {
"lib/net8.0/MassTransit.Abstractions.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/MassTransit.Abstractions.dll": {
"related": ".xml"
}
}
},
"MassTransit.RabbitMQ/8.5.5": {
"type": "package",
"dependencies": {
"MassTransit": "8.5.5",
"RabbitMQ.Client": "7.1.2"
},
"compile": {
"lib/net8.0/MassTransit.RabbitMqTransport.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/MassTransit.RabbitMqTransport.dll": {
"related": ".xml"
}
}
},
"Microsoft.AspNetCore.Authentication.Abstractions/2.3.0": {
"type": "package",
"dependencies": {
@@ -664,6 +715,38 @@
"buildTransitive/net6.0/_._": {}
}
},
"Microsoft.Extensions.Diagnostics.HealthChecks/8.0.0": {
"type": "package",
"dependencies": {
"Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "8.0.0",
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0"
},
"compile": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": {
"related": ".xml"
}
}
},
"Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/8.0.0": {
"type": "package",
"compile": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": {
"related": ".xml"
}
}
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"type": "package",
"dependencies": {
@@ -1360,6 +1443,23 @@
}
}
},
"RabbitMQ.Client/7.1.2": {
"type": "package",
"dependencies": {
"System.IO.Pipelines": "8.0.0",
"System.Threading.RateLimiting": "8.0.0"
},
"compile": {
"lib/net8.0/RabbitMQ.Client.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/RabbitMQ.Client.dll": {
"related": ".xml"
}
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {
"type": "package",
"runtimeTargets": {
@@ -2655,6 +2755,22 @@
"buildTransitive/net6.0/_._": {}
}
},
"System.Threading.RateLimiting/8.0.0": {
"type": "package",
"compile": {
"lib/net8.0/System.Threading.RateLimiting.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/System.Threading.RateLimiting.dll": {
"related": ".xml"
}
},
"build": {
"buildTransitive/net6.0/_._": {}
}
},
"System.Threading.Tasks/4.3.0": {
"type": "package",
"dependencies": {
@@ -2859,6 +2975,7 @@
"dependencies": {
"AutoMapper": "12.0.1",
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.0",
"MassTransit.RabbitMQ": "8.5.5",
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.21",
"Microsoft.AspNetCore.SignalR": "1.2.0",
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
@@ -2987,6 +3104,69 @@
"coverlet.collector.nuspec"
]
},
"MassTransit/8.5.5": {
"sha512": "bSg8k5q+rP1s+dIGXLLbctqDGdIkfDjdxwNWtCUH7xNCN9ZuM7mqSPQPIFgaYIi34e81m4FqAqo4CAHuWPkhRA==",
"type": "package",
"path": "masstransit/8.5.5",
"files": [
".nupkg.metadata",
".signature.p7s",
"NuGet.README.md",
"lib/net472/MassTransit.dll",
"lib/net472/MassTransit.xml",
"lib/net8.0/MassTransit.dll",
"lib/net8.0/MassTransit.xml",
"lib/net9.0/MassTransit.dll",
"lib/net9.0/MassTransit.xml",
"lib/netstandard2.0/MassTransit.dll",
"lib/netstandard2.0/MassTransit.xml",
"masstransit.8.5.5.nupkg.sha512",
"masstransit.nuspec",
"mt-logo-small.png"
]
},
"MassTransit.Abstractions/8.5.5": {
"sha512": "0mn2Ay17dD6z5tgSLjbVRlldSbL9iowzFEfVgVfBXVG5ttz9dSWeR4TrdD6pqH93GWXp4CvSmF8i1HqxLX7DZw==",
"type": "package",
"path": "masstransit.abstractions/8.5.5",
"files": [
".nupkg.metadata",
".signature.p7s",
"NuGet.README.md",
"lib/net472/MassTransit.Abstractions.dll",
"lib/net472/MassTransit.Abstractions.xml",
"lib/net8.0/MassTransit.Abstractions.dll",
"lib/net8.0/MassTransit.Abstractions.xml",
"lib/net9.0/MassTransit.Abstractions.dll",
"lib/net9.0/MassTransit.Abstractions.xml",
"lib/netstandard2.0/MassTransit.Abstractions.dll",
"lib/netstandard2.0/MassTransit.Abstractions.xml",
"masstransit.abstractions.8.5.5.nupkg.sha512",
"masstransit.abstractions.nuspec",
"mt-logo-small.png"
]
},
"MassTransit.RabbitMQ/8.5.5": {
"sha512": "UxWn4o90YVMF9PBkJeoskOFPneh6YtnI1fLJHtvZiSAG0eoiRrWPGa+6FQCvjkQ/ljCKfjzok2eGZc/vmNZ01A==",
"type": "package",
"path": "masstransit.rabbitmq/8.5.5",
"files": [
".nupkg.metadata",
".signature.p7s",
"NuGet.README.md",
"lib/net472/MassTransit.RabbitMqTransport.dll",
"lib/net472/MassTransit.RabbitMqTransport.xml",
"lib/net8.0/MassTransit.RabbitMqTransport.dll",
"lib/net8.0/MassTransit.RabbitMqTransport.xml",
"lib/net9.0/MassTransit.RabbitMqTransport.dll",
"lib/net9.0/MassTransit.RabbitMqTransport.xml",
"lib/netstandard2.0/MassTransit.RabbitMqTransport.dll",
"lib/netstandard2.0/MassTransit.RabbitMqTransport.xml",
"masstransit.rabbitmq.8.5.5.nupkg.sha512",
"masstransit.rabbitmq.nuspec",
"mt-logo-small.png"
]
},
"Microsoft.AspNetCore.Authentication.Abstractions/2.3.0": {
"sha512": "ve6uvLwKNRkfnO/QeN9M8eUJ49lCnWv/6/9p6iTEuiI6Rtsz+myaBAjdMzLuTViQY032xbTF5AdZF5BJzJJyXQ==",
"type": "package",
@@ -3887,6 +4067,44 @@
"useSharedDesignerContext.txt"
]
},
"Microsoft.Extensions.Diagnostics.HealthChecks/8.0.0": {
"sha512": "P9SoBuVZhJPpALZmSq72aQEb9ryP67EdquaCZGXGrrcASTNHYdrUhnpgSwIipgM5oVC+dKpRXg5zxobmF9xr5g==",
"type": "package",
"path": "microsoft.extensions.diagnostics.healthchecks/8.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"THIRD-PARTY-NOTICES.TXT",
"lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.dll",
"lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.xml",
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll",
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.xml",
"lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll",
"lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.xml",
"microsoft.extensions.diagnostics.healthchecks.8.0.0.nupkg.sha512",
"microsoft.extensions.diagnostics.healthchecks.nuspec"
]
},
"Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/8.0.0": {
"sha512": "AT2qqos3IgI09ok36Qag9T8bb6kHJ3uT9Q5ki6CySybFsK6/9JbvQAgAHf1pVEjST0/N4JaFaCbm40R5edffwg==",
"type": "package",
"path": "microsoft.extensions.diagnostics.healthchecks.abstractions/8.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"THIRD-PARTY-NOTICES.TXT",
"lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll",
"lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml",
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll",
"lib/net8.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml",
"lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll",
"lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml",
"microsoft.extensions.diagnostics.healthchecks.abstractions.8.0.0.nupkg.sha512",
"microsoft.extensions.diagnostics.healthchecks.abstractions.nuspec"
]
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"sha512": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
"type": "package",
@@ -4801,6 +5019,23 @@
"pomelo.entityframeworkcore.mysql.nuspec"
]
},
"RabbitMQ.Client/7.1.2": {
"sha512": "y3c6ulgULScWthHw5PLM1ShHRLhxg0vCtzX/hh61gRgNecL3ZC3WoBW2HYHoXOVRqTl99Br9E7CZEytGZEsCyQ==",
"type": "package",
"path": "rabbitmq.client/7.1.2",
"files": [
".nupkg.metadata",
".signature.p7s",
"README.md",
"icon.png",
"lib/net8.0/RabbitMQ.Client.dll",
"lib/net8.0/RabbitMQ.Client.xml",
"lib/netstandard2.0/RabbitMQ.Client.dll",
"lib/netstandard2.0/RabbitMQ.Client.xml",
"rabbitmq.client.7.1.2.nupkg.sha512",
"rabbitmq.client.nuspec"
]
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {
"sha512": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==",
"type": "package",
@@ -8051,6 +8286,35 @@
"useSharedDesignerContext.txt"
]
},
"System.Threading.RateLimiting/8.0.0": {
"sha512": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q==",
"type": "package",
"path": "system.threading.ratelimiting/8.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"buildTransitive/net461/System.Threading.RateLimiting.targets",
"buildTransitive/net462/_._",
"buildTransitive/net6.0/_._",
"buildTransitive/netcoreapp2.0/System.Threading.RateLimiting.targets",
"lib/net462/System.Threading.RateLimiting.dll",
"lib/net462/System.Threading.RateLimiting.xml",
"lib/net6.0/System.Threading.RateLimiting.dll",
"lib/net6.0/System.Threading.RateLimiting.xml",
"lib/net7.0/System.Threading.RateLimiting.dll",
"lib/net7.0/System.Threading.RateLimiting.xml",
"lib/net8.0/System.Threading.RateLimiting.dll",
"lib/net8.0/System.Threading.RateLimiting.xml",
"lib/netstandard2.0/System.Threading.RateLimiting.dll",
"lib/netstandard2.0/System.Threading.RateLimiting.xml",
"system.threading.ratelimiting.8.0.0.nupkg.sha512",
"system.threading.ratelimiting.nuspec",
"useSharedDesignerContext.txt"
]
},
"System.Threading.Tasks/4.3.0": {
"sha512": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"type": "package",
@@ -8541,7 +8805,7 @@
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net8.0": {
@@ -8588,8 +8852,16 @@
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.304/PortableRuntimeIdentifierGraph.json"
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
}
}
}
},
"logs": [
{
"code": "Undefined",
"level": "Warning",
"warningLevel": 1,
"message": "读取缓存文件 C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\obj\\project.nuget.cache 时遇到问题: '<' is an invalid start of a property name. Expected a '\"'. Path: $ | LineNumber: 2 | BytePositionInLine: 0."
}
]
}
+19 -2
View File
@@ -1,6 +1,6 @@
{
"version": 2,
"dgSpecHash": "/x8TFp9yNjk=",
"dgSpecHash": "j7OjEXb1ZGE=",
"success": true,
"projectFilePath": "C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\IMTest.csproj",
"expectedPackageFiles": [
@@ -8,6 +8,9 @@
"C:\\Users\\nanxun\\.nuget\\packages\\automapper.extensions.microsoft.dependencyinjection\\12.0.0\\automapper.extensions.microsoft.dependencyinjection.12.0.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\castle.core\\5.1.1\\castle.core.5.1.1.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\coverlet.collector\\6.0.0\\coverlet.collector.6.0.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\masstransit\\8.5.5\\masstransit.8.5.5.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\masstransit.abstractions\\8.5.5\\masstransit.abstractions.8.5.5.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\masstransit.rabbitmq\\8.5.5\\masstransit.rabbitmq.8.5.5.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.authentication.abstractions\\2.3.0\\microsoft.aspnetcore.authentication.abstractions.2.3.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.authentication.jwtbearer\\8.0.21\\microsoft.aspnetcore.authentication.jwtbearer.8.0.21.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\microsoft.aspnetcore.authorization\\2.3.0\\microsoft.aspnetcore.authorization.2.3.0.nupkg.sha512",
@@ -43,6 +46,8 @@
"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.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",
@@ -72,6 +77,7 @@
"C:\\Users\\nanxun\\.nuget\\packages\\nuget.frameworks\\6.5.0\\nuget.frameworks.6.5.0.nupkg.sha512",
"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\\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",
@@ -148,6 +154,7 @@
"C:\\Users\\nanxun\\.nuget\\packages\\system.text.regularexpressions\\4.3.0\\system.text.regularexpressions.4.3.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading\\4.3.0\\system.threading.4.3.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading.channels\\8.0.0\\system.threading.channels.8.0.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading.ratelimiting\\8.0.0\\system.threading.ratelimiting.8.0.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading.tasks\\4.3.0\\system.threading.tasks.4.3.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading.tasks.extensions\\4.3.0\\system.threading.tasks.extensions.4.3.0.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\system.threading.timer\\4.3.0\\system.threading.timer.4.3.0.nupkg.sha512",
@@ -162,5 +169,15 @@
"C:\\Users\\nanxun\\.nuget\\packages\\xunit.extensibility.execution\\2.5.3\\xunit.extensibility.execution.2.5.3.nupkg.sha512",
"C:\\Users\\nanxun\\.nuget\\packages\\xunit.runner.visualstudio\\2.5.3\\xunit.runner.visualstudio.2.5.3.nupkg.sha512"
],
"logs": []
"logs": [
{
"code": "Undefined",
"level": "Warning",
"message": "读取缓存文件 C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\obj\\project.nuget.cache 时遇到问题: '<' is an invalid start of a property name. Expected a '\"'. Path: $ | LineNumber: 2 | BytePositionInLine: 0.",
"projectPath": "C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\IMTest.csproj",
"warningLevel": 1,
"filePath": "C:\\Users\\nanxun\\Documents\\IM\\backend\\IMTest\\IMTest.csproj",
"targetGraphs": []
}
]
}
@@ -0,0 +1,13 @@
using IM_API.Domain.Events;
using MassTransit;
namespace IM_API.Application.EventHandlers.FriendAddHandler
{
public class FriendAddConversationHandler : IConsumer<FriendAddEvent>
{
public Task Consume(ConsumeContext<FriendAddEvent> context)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,31 @@
using IM_API.Domain.Events;
using IM_API.Models;
using MassTransit;
namespace IM_API.Application.EventHandlers.FriendAddHandler
{
public class FriendAddSignalRHandler : IConsumer<FriendAddEvent>
{
private readonly ImContext _context;
public FriendAddSignalRHandler(ImContext context)
{
_context = context;
}
public Task Consume(ConsumeContext<FriendAddEvent> context)
{
throw new NotImplementedException();
var @event = context.Message;
var RequestfriendShip = new Friend()
{
Avatar = @event.ResponseUser.Avatar,
UserId = @event.RequestUser.Id,
RemarkName = @event.RequestInfo.NickName,
Created = @event.RequestInfo.Created,
};
}
}
}
@@ -0,0 +1,63 @@
using AutoMapper;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
using IM_API.Services;
using IM_API.Tools;
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace IM_API.Application.EventHandlers.MessageCreatedHandler
{
public class ConversationEventHandler : IConsumer<MessageCreatedEvent>
{
private readonly IConversationService _conversationService;
private readonly ILogger<ConversationEventHandler> _logger;
private readonly ImContext _context;
private readonly IMapper _mapper;
public ConversationEventHandler(
IConversationService conversationService,
ILogger<ConversationEventHandler> logger,
ImContext imContext,
IMapper mapper
)
{
_conversationService = conversationService;
_logger = logger;
_context = imContext;
_mapper = mapper;
}
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
{
var @event = context.Message;
if (@event.ChatType == ChatType.PRIVATE)
{
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();
}
}
}
}
@@ -0,0 +1,43 @@
using AutoMapper;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Hubs;
using IM_API.Models;
using IM_API.Tools;
using MassTransit;
using Microsoft.AspNetCore.SignalR;
namespace IM_API.Application.EventHandlers.MessageCreatedHandler
{
public class SignalREventHandler : IConsumer<MessageCreatedEvent>
{
private readonly IHubContext<ChatHub> _hub;
private readonly IMapper _mapper;
public SignalREventHandler(IHubContext<ChatHub> hub, IMapper mapper)
{
_hub = hub;
_mapper = mapper;
}
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
{
var @event = context.Message;
if (@event.ChatType == Models.ChatType.PRIVATE)
{
MessageBaseDto messageBaseDto = new MessageBaseDto
{
MsgId = @event.MessageId.ToString(),
ChatType = @event.ChatType.ToString(),
Content = @event.MessageContent,
GroupMemberId = null,
ReceiverId = @event.MsgRecipientId,
SenderId = @event.MsgSenderId,
TimeStamp = @event.MessageCreated,
Type = @event.MessageMsgType.ToString()
};
await _hub.Clients.Users(@event.MsgRecipientId.ToString()).SendAsync("ReceiveMessage", messageBaseDto);
}
}
}
}
@@ -0,0 +1,9 @@
using IM_API.Domain.Interfaces;
namespace IM_API.Application.Interfaces
{
public interface IEventBus
{
Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent;
}
}
@@ -0,0 +1,9 @@
using IM_API.Domain.Interfaces;
namespace IM_API.Application.Interfaces
{
public interface IEventHandler<in TEvent> where TEvent : IEvent
{
Task Handle(TEvent @event);
}
}
+40
View File
@@ -0,0 +1,40 @@
using IM_API.Application.EventHandlers.FriendAddHandler;
using IM_API.Application.EventHandlers.MessageCreatedHandler;
using MassTransit;
namespace IM_API.Configs
{
public static class MQConfig
{
public static IServiceCollection AddRabbitMQ(this IServiceCollection services, RabbitMqOptions options)
{
services.AddMassTransit(x =>
{
x.AddConsumer<ConversationEventHandler>();
x.AddConsumer<SignalREventHandler>();
x.AddConsumer<FriendAddConversationHandler>();
x.AddConsumer<FriendAddSignalRHandler>();
x.UsingRabbitMq((ctx,cfg) =>
{
cfg.Host(options.Host, "/", h =>
{
h.Username(options.Username);
h.Password(options.Password);
});
});
});
return services;
}
}
public class RabbitMqOptions
{
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get;set; }
}
}
+67 -8
View File
@@ -1,4 +1,5 @@
using AutoMapper;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Models;
@@ -25,20 +26,22 @@ namespace IM_API.Configs
.ForMember(dest => dest.IsDeleted,opt => opt.MapFrom(src => 0))
;
//好友信息模型转换
CreateMap<Friend, FriendInfoDto>();
CreateMap<Friend, FriendInfoDto>()
.ForMember(dest => dest.UserInfo, opt => opt.MapFrom(src => src.FriendNavigation))
;
//好友请求通过后新增好友关系
CreateMap<FriendRequest, Friend>()
.ForMember(dest => dest.UserId , opt => opt.MapFrom(src => src.RequestUser))
.ForMember(dest => dest.FriendId , opt => opt.MapFrom(src => src.ResponseUser))
.ForMember(dest => dest.StatusEnum , opt =>opt.MapFrom(src => FriendStatus.Added))
.ForMember(dest => dest.RemarkName , opt => opt.MapFrom(src => src.ResponseUserNavigation.NickName))
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.Now))
CreateMap<FriendRequestDto, Friend>()
.ForMember(dest => dest.UserId , opt => opt.MapFrom(src => src.FromUserId))
.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))
;
//发起好友请求转换请求对象
CreateMap<FriendRequestDto, FriendRequest>()
.ForMember(dest => dest.RequestUser , opt => opt.MapFrom(src => src.FromUserId))
.ForMember(dest => dest.ResponseUser , opt => opt.MapFrom(src => src.ToUserId))
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.Now))
.ForMember(dest => dest.Created , opt => opt.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.StateEnum , opt => opt.MapFrom(src => FriendRequestState.Pending))
.ForMember(dest => dest.Description , opt => opt.MapFrom(src => src.Description))
;
@@ -51,6 +54,7 @@ namespace IM_API.Configs
.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))
;
CreateMap<MessageBaseDto, Message>()
.ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.SenderId))
@@ -59,11 +63,66 @@ namespace IM_API.Configs
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.TimeStamp))
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
.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())
;
//会话对象深拷贝
CreateMap<Conversation, Conversation>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.UserId, opt => opt.Ignore())
.ForMember(dest => dest.TargetId, opt => opt.Ignore())
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
.ForMember(dest => dest.StreamKey, opt => opt.Ignore())
;
//消息对象转消息创建事件对象
CreateMap<Message, MessageCreatedEvent>()
.ForMember(dest => dest.MessageMsgType, opt => opt.MapFrom(src => src.MsgTypeEnum))
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatTypeEnum))
.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.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
;
//消息发送事件转换会话对象
CreateMap<MessageCreatedEvent, Conversation>()
.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.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))
;
//创建会话对象
CreateMap<Conversation, ConversationDto>()
.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.DateTime, opt => opt.MapFrom(src => src.LastMessageTime))
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.TargetId))
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => src.UnreadCount))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId));
CreateMap<Friend, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.RemarkName));
CreateMap<Group, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
}
@@ -1,5 +1,13 @@
using IM_API.Interface.Services;
using IM_API.Application.EventHandlers;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Infrastructure.EventBus;
using IM_API.Interface.Services;
using IM_API.Services;
using IM_API.Tools;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
namespace IM_API.Configs
{
@@ -7,14 +15,37 @@ namespace IM_API.Configs
{
public static IServiceCollection AddAllService(this IServiceCollection services, IConfiguration configuration)
{
services.AddAutoMapper(typeof(MapperConfig));
services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IUserService, UserService>();
services.AddTransient<IFriendSerivce, FriendService>();
services.AddTransient<IMessageSevice, MessageService>();
services.AddTransient<IConversationService, ConversationService>();
services.AddScoped<IEventBus, InMemoryEventBus>();
services.AddSingleton<IJWTService, JWTService>();
services.AddSingleton<IRefreshTokenService,RedisRefreshTokenService>();
services.AddSingleton<IRefreshTokenService, RedisRefreshTokenService>();
return services;
}
}
public static IServiceCollection AddModelValidation(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var errors = context.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new
{
Field = e.Key,
Message = e.Value.Errors.First().ErrorMessage
});
return new BadRequestObjectResult(new BaseResponse<object?>(CodeDefine.PARAMETER_ERROR.Code, errors.First().Message));
};
});
return services;
}
}
}
+13 -4
View File
@@ -1,4 +1,5 @@
using IM_API.Dtos;
using AutoMapper;
using IM_API.Dtos;
using IM_API.Interface.Services;
using IM_API.Tools;
using Microsoft.AspNetCore.Http;
@@ -17,7 +18,12 @@ namespace IM_API.Controllers
private readonly IJWTService _jwtService;
private readonly IRefreshTokenService _refreshTokenService;
private readonly IConfiguration _configuration;
public AuthController(ILogger<AuthController> logger, IAuthService authService, IJWTService jwtService, IRefreshTokenService refreshTokenService, IConfiguration configuration,IUserService userService)
private IMapper _mapper;
public AuthController(ILogger<AuthController> logger, IAuthService authService,
IJWTService jwtService, IRefreshTokenService refreshTokenService,
IConfiguration configuration,IUserService userService,
IMapper mapper
)
{
_logger = logger;
_authService = authService;
@@ -25,16 +31,18 @@ namespace IM_API.Controllers
_refreshTokenService = refreshTokenService;
_configuration = configuration;
_userService = userService;
_mapper = mapper;
}
[HttpPost]
public async Task<IActionResult> Login(LoginRequestDto dto)
{
var user = await _authService.LoginAsync(dto);
var userInfo = _mapper.Map<UserInfoDto>(user);
//生成凭证
(string token,DateTime expiresAt) = _jwtService.CreateAccessTokenForUser(user.Id,user.Username,"user");
//生成刷新凭证
string refreshToken = await _refreshTokenService.CreateRefreshTokenAsync(user.Id);
var res = new BaseResponse<LoginDto>(new LoginDto(user.Id,token,refreshToken,expiresAt));
var res = new BaseResponse<LoginDto>(new LoginDto(userInfo,token,refreshToken, expiresAt));
return Ok(res);
}
[HttpPost]
@@ -45,6 +53,7 @@ namespace IM_API.Controllers
return Ok(res);
}
[HttpPost]
[ProducesResponseType(typeof(BaseResponse<LoginDto>),StatusCodes.Status200OK)]
public async Task<IActionResult> Refresh(RefreshDto dto)
{
(bool ok,int userId) = await _refreshTokenService.ValidateRefreshTokenAsync(dto.refreshToken);
@@ -55,7 +64,7 @@ namespace IM_API.Controllers
}
var userinfo = await _userService.GetUserInfoAsync(userId);
(string token,DateTime expiresAt) = _jwtService.CreateAccessTokenForUser(userinfo.Id,userinfo.Username,"user");
var res = new BaseResponse<LoginDto>(new LoginDto(userinfo.Id,token,dto.refreshToken,expiresAt));
var res = new BaseResponse<LoginDto>(new LoginDto(userinfo,token, dto.refreshToken, expiresAt));
return Ok(res);
}
}
@@ -0,0 +1,55 @@
using IM_API.Dtos;
using IM_API.Interface.Services;
using IM_API.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace IM_API.Controllers
{
[Route("api/[controller]/[action]")]
[Authorize]
[ApiController]
public class ConversationController : ControllerBase
{
private readonly IConversationService _conversationSerivice;
private readonly ILogger<ConversationController> _logger;
public ConversationController(IConversationService conversationSerivice, ILogger<ConversationController> logger)
{
_conversationSerivice = conversationSerivice;
_logger = logger;
}
[HttpGet]
public async Task<IActionResult> List()
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var list = await _conversationSerivice.GetConversationsAsync(int.Parse(userIdStr));
var res = new BaseResponse<List<ConversationDto>>(list);
return Ok(res);
}
[HttpGet]
public async Task<IActionResult> Get([FromQuery]int conversationId)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var conversation = await _conversationSerivice.GetConversationByIdAsync(int.Parse(userIdStr), conversationId);
var res = new BaseResponse<ConversationDto>(conversation);
return Ok(res);
}
[HttpPost]
public async Task<IActionResult> Clear()
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
await _conversationSerivice.ClearConversationsAsync(int.Parse(userIdStr));
return Ok(new BaseResponse<object?>());
}
[HttpPost]
public async Task<IActionResult> Delete(int cid)
{
await _conversationSerivice.DeleteConversationAsync(cid);
return Ok(new BaseResponse<object?>());
}
}
}
@@ -44,12 +44,12 @@ namespace IM_API.Controllers
/// <param name="desc"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Requests(bool isReceived,int page,int limit,bool desc)
public async Task<IActionResult> Requests(int page,int limit,bool desc)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
int userId = int.Parse(userIdStr);
var list = await _friendService.GetFriendRequestListAsync(userId,isReceived,page,limit,desc);
var res = new BaseResponse<List<FriendRequest>>(list);
var list = await _friendService.GetFriendRequestListAsync(userId,page,limit,desc);
var res = new BaseResponse<List<FriendRequestResDto>>(list);
return Ok(res);
}
/// <summary>
@@ -59,13 +59,15 @@ namespace IM_API.Controllers
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> HandleRequest([FromRoute]int id, [FromBody]FriendRequestHandleDto dto)
public async Task<IActionResult> HandleRequest(
[FromRoute]int id, [FromBody]FriendRequestHandleDto dto
)
{
await _friendService.HandleFriendRequestAsync(new HandleFriendRequestDto()
{
RequestId = id,
RemarkName = dto.remarkName,
Action = dto.action
RemarkName = dto.RemarkName,
Action = dto.Action
});
var res = new BaseResponse<object?>();
return Ok(res);
@@ -0,0 +1,46 @@
using IM_API.Dtos;
using IM_API.Interface.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace IM_API.Controllers
{
[Authorize]
[Route("api/[controller]/[action]")]
[ApiController]
public class MessageController : ControllerBase
{
private readonly IMessageSevice _messageService;
private readonly ILogger<MessageController> _logger;
public MessageController(IMessageSevice messageService, ILogger<MessageController> logger)
{
_messageService = messageService;
_logger = logger;
}
[HttpPost]
public async Task<IActionResult> SendPrivateMessage(MessageBaseDto dto)
{
var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier);
await _messageService.SendPrivateMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
return Ok(new BaseResponse<object?>());
}
[HttpPost]
public async Task<IActionResult> SendGroupMessage(MessageBaseDto dto)
{
var userIdstr = User.FindFirstValue(ClaimTypes.NameIdentifier);
await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
return Ok(new BaseResponse<object?>());
}
[HttpGet]
public async Task<IActionResult> GetMessageList([Required]int conversationId, int? msgId, int? pageSize)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr), conversationId, msgId, pageSize, false);
var res = new BaseResponse<List<MessageBaseDto>>(msgList);
return Ok(res);
}
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ namespace IM_API.Controllers
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
int userId = int.Parse(userIdStr);
await _userService.ResetPasswordAsync(userId,dto.oldPassword,dto.Password);
await _userService.ResetPasswordAsync(userId,dto.OldPassword,dto.Password);
return Ok(new BaseResponse<object?>());
}
/// <summary>
@@ -0,0 +1,13 @@
using IM_API.Domain.Interfaces;
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 long OperatorId { get; init; }
public string AggregateId { get; init; } = "";
public abstract string EventType { get; }
}
}
@@ -0,0 +1,24 @@
using IM_API.Dtos;
namespace IM_API.Domain.Events
{
public record FriendAddEvent:DomainEvent
{
public override string EventType => "IM.FRIENDS_FRIEND_ADD";
/// <summary>
/// 发起请求用户
/// </summary>
public UserInfoDto RequestUser { get; set; }
/// <summary>
/// 接受请求用户
/// </summary>
public UserInfoDto ResponseUser { get; set; }
public FriendRequestResDto RequestInfo { get; set; }
/// <summary>
/// 好友关系创建时间
/// </summary>
public DateTime Created { get; set; }
}
}
@@ -0,0 +1,22 @@
using IM_API.Dtos;
using IM_API.Models;
namespace IM_API.Domain.Events
{
public record MessageCreatedEvent : DomainEvent
{
public override string EventType => "IM.MESSAGE.MESSAGE_CREATED";
public ChatType ChatType { get; set; }
public MessageMsgType MessageMsgType { get; set; }
public int MessageId { 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 string StreamKey { get; set; }
}
}
@@ -0,0 +1,6 @@
namespace IM_API.Domain.Interfaces
{
public interface IEvent
{
}
}
+5 -1
View File
@@ -73,6 +73,10 @@ namespace IM_API.Dtos
this.Code = codeDefine.Code;
this.Message = codeDefine.Message;
}
public BaseResponse() { }
public BaseResponse()
{
this.Code = CodeDefine.SUCCESS.Code;
this.Message = CodeDefine.SUCCESS.Message;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using IM_API.Models;
namespace IM_API.Dtos
{
public class ConversationDto
{
public int Id { get; set; }
/// <summary>
/// 用户
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 对方ID(群聊为群聊ID,单聊为单聊ID)
/// </summary>
public int TargetId { get; set; }
/// <summary>
/// 最后一条未读消息ID
/// </summary>
public int? LastReadMessageId { get; set; }
/// <summary>
/// 未读消息数
/// </summary>
public int UnreadCount { get; set; }
public int ChatType { get; set; }
/// <summary>
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; } = null!;
/// <summary>
/// 最后一条消息时间
/// </summary>
public DateTime? DateTime { get; set; } = null;
/// <summary>
/// 对方昵称
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// 对方头像
/// </summary>
public string? TargetAvatar { get; set; }
public MessageBaseDto? LastReadMessage { get; set; }
}
}
+27 -2
View File
@@ -1,7 +1,32 @@
using IM_API.Models;
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public record FriendInfoDto(int Id, int UserId,int FriendId,FriendStatus StatusEnum,DateTime Created,string RemarkName,string? Avatar);
public record FriendRequestHandleDto(HandleFriendRequestAction action,string? remarkName);
public record FriendInfoDto
{
public int Id { get; init; }
public int UserId { get; init; }
public int FriendId { get; init; }
public FriendStatus StatusEnum { get; init; }
public DateTime Created { get; init; }
public string RemarkName { get; init; } = string.Empty;
public string? Avatar { get; init; }
public UserInfoDto UserInfo { get; init; }
}
public record FriendRequestHandleDto
{
[Required(ErrorMessage = "操作必填")]
public HandleFriendRequestAction Action { get; init; }
[StringLength(20, ErrorMessage = "备注名最大20个字符")]
public string? RemarkName { get; init; }
}
}
+7 -2
View File
@@ -1,10 +1,15 @@
namespace IM_API.Dtos
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public class FriendRequestDto
{
public int FromUserId { get; set; }
public int? FromUserId { get; set; }
public int ToUserId { get; set; }
[Required(ErrorMessage = "备注名必填")]
[StringLength(20, ErrorMessage = "备注名不能超过20位字符")]
public string? RemarkName { get; set; }
[StringLength(50, ErrorMessage = "描述不能超过50字符")]
public string? Description { get; set; }
}
}
@@ -0,0 +1,36 @@
using IM_API.Models;
namespace IM_API.Dtos
{
public class FriendRequestResDto
{
public int Id { get; set; }
/// <summary>
/// 申请人
/// </summary>
public int RequestUser { get; set; }
/// <summary>
/// 被申请人
/// </summary>
public int ResponseUser { get; set; }
public string Avatar { get; set; }
public string NickName { get; set; }
/// <summary>
/// 申请时间
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// 申请附言
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 申请状态(0:待通过,1:拒绝,2:同意,3:拉黑)
/// </summary>
public FriendRequestState State { get; set; }
}
}
+3 -3
View File
@@ -2,12 +2,12 @@
{
public class LoginDto
{
public int Id { get; set; }
public UserInfoDto userInfo { get; set; }
public string Token { get; set; }
public string RefreshToken { get; set; }
public DateTime ExpireAt { get; set; }
public LoginDto(int id,string token,string refreshToken,DateTime expireAt) {
this.Id = id;
public LoginDto(UserInfoDto userInfo,string token, string refreshToken, DateTime expireAt) {
this.userInfo = userInfo;
this.Token = token;
this.RefreshToken = refreshToken;
this.ExpireAt = expireAt;
+10 -1
View File
@@ -1,8 +1,17 @@
namespace IM_API.Dtos
using IM_API.Tools;
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public class LoginRequestDto
{
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(20, ErrorMessage = "用户名不能超过20字符")]
[RegularExpression(@"^[A-Za-z0-9]+$",ErrorMessage = "")]
public string Username { get; set; }
[Required(ErrorMessage = "密码不能为空")]
[StringLength(50, ErrorMessage = "密码不能超过50字符")]
public string Password { get; set; }
}
}
+13 -2
View File
@@ -1,5 +1,16 @@
namespace IM_API.Dtos
{
public record MessageBaseDto(
string Type,string ChatType, string? MsgId,int SenderId,int ReceiverId,string Content,DateTime TimeStamp);
public record MessageBaseDto
{
// 使用 { get; init; } 确保对象创建后不可修改,且支持无参构造
public string Type { get; init; } = default!;
public string ChatType { get; init; } = default!;
public string? 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 MessageBaseDto() { }
}
}
+11 -1
View File
@@ -1,9 +1,19 @@
namespace IM_API.Dtos
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public class RegisterRequestDto
{
[Required(ErrorMessage = "用户名不能为空")]
[MaxLength(20, ErrorMessage = "用户名不能超过20字符")]
[RegularExpression(@"^[A-Za-z0-9]+$", ErrorMessage = "")]
public string Username { get; set; }
[Required(ErrorMessage = "密码不能为空")]
[MaxLength(50, ErrorMessage = "密码不能超过50字符")]
public string Password { get; set; }
[Required(ErrorMessage = "昵称不能为空")]
[MaxLength(20, ErrorMessage = "昵称不能超过20字符")]
public string? NickName { get; set; }
}
}
+4 -1
View File
@@ -1,7 +1,10 @@
namespace IM_API.Dtos
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public class UpdateUserDto
{
[MaxLength(20, ErrorMessage = "昵称不能超过50字符")]
public string? NickName { get; set; }
public string? Avatar { get; set; }
+16 -2
View File
@@ -1,7 +1,21 @@
using IM_API.Models;
using System.ComponentModel.DataAnnotations;
namespace IM_API.Dtos
{
public record PasswordResetDto(string oldPassword,string Password);
public record OnlineStatusSetDto(UserOnlineStatus OnlineStatus);
public record PasswordResetDto
{
[Required(ErrorMessage = "旧密码不能为空")]
public string OldPassword { get; init; }
[Required(ErrorMessage = "新密码不能为空")]
[MaxLength(50, ErrorMessage = "密码不能超过50个字符")]
public string Password { get; init; }
}
public record OnlineStatusSetDto
{
[Required]
public UserOnlineStatus OnlineStatus { get; init; }
}
}
+4
View File
@@ -17,6 +17,10 @@ namespace IM_API.Dtos
/// 用户昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 用户头像
/// </summary>
public string? Avatar { get; set; }
/// <summary>
/// 用户在线状态
+41 -6
View File
@@ -1,5 +1,9 @@
using IM_API.Dtos;
using AutoMapper;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Interface.Services;
using IM_API.Models;
using IM_API.Tools;
using Microsoft.AspNetCore.SignalR;
using System.Security.Claims;
@@ -9,22 +13,34 @@ namespace IM_API.Hubs
public class ChatHub:Hub
{
private IMessageSevice _messageService;
public ChatHub(IMessageSevice messageService)
private readonly IConversationService _conversationService;
private readonly IEventBus _eventBus;
private readonly IMapper _mapper;
public ChatHub(IMessageSevice messageService, IConversationService conversationService, IEventBus eventBus, IMapper mapper)
{
_messageService = messageService;
_conversationService = conversationService;
_eventBus = eventBus;
_mapper = mapper;
}
public async override Task OnConnectedAsync()
{
if (!Context.User.Identity.IsAuthenticated)
{
await Clients.Caller.SendAsync("ReceiveMessage",new BaseResponse<object?>(CodeDefine.AUTH_FAILED));
Context.Abort();
return;
}
//将用户加入已加入聊天的会话组
string userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var keys = await _conversationService.GetUserAllStreamKeyAsync(int.Parse(userIdStr));
foreach(var key in keys)
{
await Groups.AddToGroupAsync(Context.ConnectionId,key);
}
await base.OnConnectedAsync();
}
public async Task SendPrivateMessage(MessageBaseDto dto)
public async Task SendMessage(MessageBaseDto dto)
{
if (!Context.User.Identity.IsAuthenticated)
{
@@ -33,8 +49,27 @@ namespace IM_API.Hubs
return;
}
var userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
await _messageService.SendPrivateMessageAsync(int.Parse(userIdStr),dto.ReceiverId,dto);
await Clients.Users(dto.ReceiverId.ToString()).SendAsync("ReceiveMessage", userIdStr, dto.Content);
MessageBaseDto msgInfo = null;
if(dto.ChatType.ToLower() == ChatType.PRIVATE.ToString().ToLower())
{
msgInfo = await _messageService.SendPrivateMessageAsync(int.Parse(userIdStr), dto.ReceiverId, dto);
}
else
{
msgInfo = await _messageService.SendGroupMessageAsync(int.Parse(userIdStr), dto.ReceiverId, dto);
}
return;
}
public async Task ClearUnreadCount(int conversationId)
{
if (!Context.User.Identity.IsAuthenticated)
{
await Clients.Caller.SendAsync("ReceiveMessage", new BaseResponse<object?>(CodeDefine.AUTH_FAILED));
Context.Abort();
return;
}
var userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
await _conversationService.ClearUnreadCountAsync(int.Parse(userIdStr), conversationId);
return;
}
}
+1
View File
@@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.21" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.21">
@@ -0,0 +1,32 @@
using IM_API.Application.Interfaces;
using IM_API.Domain.Interfaces;
namespace IM_API.Infrastructure.EventBus
{
public class InMemoryEventBus : IEventBus
{
private IServiceProvider _serviceProvider;
private ILogger<InMemoryEventBus> _logger;
public InMemoryEventBus(IServiceProvider serviceProvider, ILogger<InMemoryEventBus> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent
{
var handlers = _serviceProvider.GetServices<IEventHandler<TEvent>>();
foreach (var handler in handlers)
{
try
{
await handler.Handle(@event);
}
catch(Exception e)
{
_logger.LogError("EventHandler error:"+e.Message, e);
}
}
}
}
}
@@ -10,12 +10,37 @@ namespace IM_API.Interface.Services
/// </summary>
/// <param name="clearConversationsDto"></param>
/// <returns></returns>
Task<bool> ClearConversationsAsync(ClearConversationsDto clearConversationsDto);
Task<bool> ClearConversationsAsync(int userId);
/// <summary>
/// 删除单个聊天会话
/// </summary>
/// <param name="conversationId"></param>
/// <returns></returns>
Task<bool> DeleteConversationAsync(int conversationId);
/// <summary>
/// 获取用户当前消息会话
/// </summary>
/// <param name="userId">用户id</param>
/// <returns></returns>
Task<Conversation> GetConversationsAsync(int userId);
Task<List<ConversationDto>> GetConversationsAsync(int userId);
/// <summary>
/// 获取指定用户的所有推送标识符
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
Task<List<string>> GetUserAllStreamKeyAsync(int userId);
/// <summary>
/// 获取单个conversation信息
/// </summary>
/// <param name="conversationId"></param>
/// <returns></returns>
Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId);
/// <summary>
/// 清空未读消息
/// </summary>
/// <param name="userId"></param>
/// <param name="conversationId"></param>
/// <returns></returns>
Task<bool> ClearUnreadCountAsync(int userId, int conversationId);
}
}
@@ -27,7 +27,7 @@ namespace IM_API.Interface.Services
/// <param name="page"></param>
/// <param name="limit"></param>
/// <returns></returns>
Task<List<FriendRequest>> GetFriendRequestListAsync(int userId,bool isReceived,int page,int limit, bool desc);
Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId,int page,int limit, bool desc);
/// <summary>
/// 处理好友请求
/// </summary>
@@ -11,7 +11,7 @@ namespace IM_API.Interface.Services
/// <param name="receiverId">接收人</param>
/// <param name="dto"></param>
/// <returns></returns>
Task<bool> SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto);
Task<MessageBaseDto> SendPrivateMessageAsync(int senderId,int receiverId,MessageBaseDto dto);
/// <summary>
/// 发送群聊消息
/// </summary>
@@ -19,26 +19,16 @@ namespace IM_API.Interface.Services
/// <param name="groupId">接收群id</param>
/// <param name="dto"></param>
/// <returns></returns>
Task<bool> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
Task<MessageBaseDto> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
/// <summary>
/// 获取私聊消息列表
/// 获取消息列表
/// </summary>
/// <param name="userAId"></param>
/// <param name="userBId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="conversationId">会话id(用于获取指定用户间聊天消息)</param>
/// <param name="msgId">消息id</param>
/// <param name="pageSize">获取消息数量</param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId,int userBId,int page,int pageSize,bool desc);
/// <summary>
/// 获取群聊消息列表
/// </summary>
/// <param name="groupId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc);
Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId,int? msgId,int? pageSize,bool desc);
/// <summary>
/// 获取未读消息数
/// </summary>
+20 -8
View File
@@ -18,21 +18,33 @@ public partial class Conversation
public int TargetId { get; set; }
/// <summary>
/// 消息类型(同Messages.MsgType
/// 最后一条未读消息ID
/// </summary>
public int MsgType { get; set; }
/// <summary>
/// 最后一条消息ID
/// </summary>
public int LastMessageId { get; set; }
public int? LastReadMessageId { get; set; }
/// <summary>
/// 未读消息数
/// </summary>
public int UnreadCount { get; set; }
public virtual Message LastMessage { get; set; } = null!;
public int ChatType { get; set; }
/// <summary>
/// 消息推送唯一标识符
/// </summary>
public string StreamKey { get; set; } = null!;
/// <summary>
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; } = null!;
/// <summary>
/// 最后一条消息发送时间
/// </summary>
public DateTime LastMessageTime { get; set; }
public virtual Message? LastReadMessage { get; set; }
public virtual User User { get; set; } = null!;
}
+5
View File
@@ -44,6 +44,11 @@ public partial class Group
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// 群头像
/// </summary>
public string Avatar { get; set; } = null!;
public virtual ICollection<GroupInvite> GroupInvites { get; set; } = new List<GroupInvite>();
public virtual User GroupMasterNavigation { get; set; } = null!;
+25 -26
View File
@@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
namespace IM_API.Models;
public partial class ImContext : DbContext
{
public ImContext()
{
}
public ImContext(DbContextOptions<ImContext> options)
: base(options)
{
@@ -49,17 +44,6 @@ public partial class ImContext : DbContext
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySql(
"server=frp-era.com;port=26582;database=IM;user=product;password=12345678",
ServerVersion.Parse("5.7.44-mysql")
);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -117,18 +101,25 @@ public partial class ImContext : DbContext
entity.HasIndex(e => e.UserId, "Userid");
entity.HasIndex(e => e.LastMessageId, "lastMessageId");
entity.HasIndex(e => e.LastReadMessageId, "lastMessageId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.LastMessageId)
.HasComment("最后一条消息ID ")
entity.Property(e => e.ChatType).HasColumnType("int(11)");
entity.Property(e => e.LastMessage)
.HasMaxLength(255)
.HasComment("最后一条最新消息");
entity.Property(e => e.LastMessageTime)
.HasComment("最后一条消息发送时间")
.HasColumnType("datetime");
entity.Property(e => e.LastReadMessageId)
.HasComment("最后一条未读消息ID ")
.HasColumnType("int(11)")
.HasColumnName("lastMessageId");
entity.Property(e => e.MsgType)
.HasComment("消息类型(同Messages.MsgType ")
.HasColumnType("int(11)");
.HasColumnName("lastReadMessageId");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.Property(e => e.TargetId)
.HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) ")
.HasColumnType("int(11)");
@@ -139,9 +130,8 @@ public partial class ImContext : DbContext
.HasComment("用户")
.HasColumnType("int(11)");
entity.HasOne(d => d.LastMessage).WithMany(p => p.Conversations)
.HasForeignKey(d => d.LastMessageId)
.OnDelete(DeleteBehavior.ClientSetNull)
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)
@@ -337,6 +327,9 @@ public partial class ImContext : DbContext
entity.Property(e => e.Auhority)
.HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("群头像");
entity.Property(e => e.Created)
.HasComment("群聊创建时间")
.HasColumnType("datetime");
@@ -545,6 +538,9 @@ 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)");
@@ -557,6 +553,9 @@ public partial class ImContext : DbContext
entity.Property(e => e.State)
.HasComment("消息状态(0:已发送,1:已撤回) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.HasOne(d => d.SenderNavigation).WithMany(p => p.Messages)
.HasForeignKey(d => d.Sender)
+10
View File
@@ -44,6 +44,16 @@ public partial class Message
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// 消息推送唯一标识符
/// </summary>
public string StreamKey { get; set; } = null!;
/// <summary>
/// 若为群消息则表示具体的成员id
/// </summary>
public int? GroupMemberId { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
public virtual ICollection<File> Files { get; set; } = new List<File>();
+1 -1
View File
@@ -20,7 +20,7 @@ public partial class User
/// <summary>
/// 用户昵称
/// </summary>
public string? NickName { get; set; } = null!;
public string? NickName { get; set; }
/// <summary>
/// 用户在线状态
+16 -6
View File
@@ -3,6 +3,7 @@ using IM_API.Configs;
using IM_API.Filters;
using IM_API.Hubs;
using IM_API.Models;
using IM_API.Tools;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@@ -32,6 +33,8 @@ namespace IM_API
//注入redis
var redis = ConnectionMultiplexer.Connect(redisConStr);
builder.Services.AddSingleton<IConnectionMultiplexer>(redis);
builder.Services.AddRabbitMQ(configuration.GetSection("RabbitMqOptions").Get<RabbitMqOptions>());
builder.Services.AddAllService(configuration);
@@ -44,7 +47,13 @@ namespace IM_API
policy.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin();
.AllowCredentials()
.SetIsOriginAllowed(origin =>
{
// 允许所有来自本地或特定网段的请求
var host = new Uri(origin).Host;
return host == "localhost" || host.StartsWith("192.168.");
});
});
});
//凭证处理
@@ -86,11 +95,7 @@ namespace IM_API
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
options.Events = new JwtBearerEvents
{
},
OnAuthenticationFailed = context =>
{
Console.WriteLine("Authentication failed: " + context.Exception.Message);
@@ -101,7 +106,12 @@ namespace IM_API
builder.Services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
}).AddJsonOptions(options =>
{
// 保持 ISO 8601 格式
options.JsonSerializerOptions.Converters.Add(new UtcDateTimeConverter());
});
builder.Services.AddModelValidation(configuration);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@@ -0,0 +1,138 @@
using AutoMapper;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
using IM_API.Tools;
using Microsoft.EntityFrameworkCore;
namespace IM_API.Services
{
public class ConversationService : IConversationService
{
private readonly ImContext _context;
private readonly IMapper _mapper;
public ConversationService(ImContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
#region
public async Task<bool> ClearConversationsAsync(int userId)
{
await _context.Conversations.Where(x => x.UserId == userId).ExecuteDeleteAsync();
return true;
}
#endregion
#region
public async Task<List<ConversationDto>> 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
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
select new { c, g.Avatar, g.Name })
.ToListAsync();
var privateDtos = privateList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.RemarkName;
return dto;
});
var groupDtos = groupList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.Name;
return dto;
});
// 4. 合并并排序
return privateDtos.Concat(groupDtos)
.OrderByDescending(x => x.DateTime)
.ToList();
}
#endregion
#region
public async Task<bool> DeleteConversationAsync(int conversationId)
{
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.Id == conversationId);
if (conversation == null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
_context.Conversations.Remove(conversation);
await _context.SaveChangesAsync();
return true;
}
#endregion
#region
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
{
return await _context.Conversations.Where(x => x.UserId == userId)
.Select(x => x.StreamKey)
.Distinct()
.ToListAsync();
}
#endregion
#region
public async Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId)
{
var conversation = await _context.Conversations
.Include(x => x.LastReadMessage)
.FirstOrDefaultAsync(
x => x.UserId == userId && x.Id == conversationId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var dto = _mapper.Map<ConversationDto>(conversation);
//dto.LastReadMessage = _mapper.Map<MessageBaseDto>(conversation);
if(conversation.ChatType == (int)ChatType.PRIVATE)
{
var friendInfo = await _context.Friends.FirstOrDefaultAsync(
x => x.UserId == 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)
{
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
x => x.Id == conversation.TargetId
);
if (groupInfo is null) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
_mapper.Map(groupInfo, dto);
}
return dto;
}
#endregion
public async Task<bool> ClearUnreadCountAsync(int userId, int conversationId)
{
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.UserId == userId && x.Id == conversationId);
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)
.FirstOrDefaultAsync();
conversation.LastReadMessage = message;
conversation.UnreadCount = 0;
_context.Conversations.Update(conversation);
await _context.SaveChangesAsync();
return true;
}
}
}
+29 -24
View File
@@ -63,33 +63,39 @@ namespace IM_API.Services
public async Task<List<FriendInfoDto>> GetFriendListAsync(int userId, int page, int limit, bool desc)
{
var query = _context.Friends.Where(x => x.UserId == userId && x.Status == (sbyte)FriendStatus.Added);
var query = _context.Friends.Include(u => u.FriendNavigation).Where(x => x.UserId == userId && x.Status == (sbyte)FriendStatus.Added);
if (desc)
{
query = query.OrderByDescending(x => x.UserId);
}
var friendList = await query.Skip((page - 1 * limit)).Take(limit).ToListAsync();
var friendList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
return _mapper.Map<List<FriendInfoDto>>(friendList);
}
#endregion
#region
public async Task<List<FriendRequest>> GetFriendRequestListAsync(int userId, bool isReceived, int page, int limit, bool desc)
public async Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId, int page, int limit, bool desc)
{
var query = _context.FriendRequests.AsQueryable();
//是否为请求方
if (isReceived)
{
query = _context.FriendRequests.Where(x => x.ResponseUser == userId);
}
else
{
query = _context.FriendRequests.Where(x => x.RequestUser == userId);
}
if (desc)
{
query = query.OrderByDescending(x => x.Id);
}
var friendRequestList = await query.Skip((page - 1 * limit)).Take(limit).ToListAsync();
var query = _context.FriendRequests
.Include(x => x.ResponseUserNavigation)
.Include(x => x.RequestUserNavigation)
.Where(
x => (x.ResponseUser == userId) ||
x.RequestUser == userId
)
.Select(s => new FriendRequestResDto
{
Id = s.Id,
RequestUser = s.RequestUser,
ResponseUser = s.ResponseUser,
Avatar = s.RequestUser == userId ? s.ResponseUserNavigation.Avatar : s.RequestUserNavigation.Avatar,
Created = s.Created,
NickName = s.RequestUser == userId ? s.ResponseUserNavigation.NickName : s.RequestUserNavigation.NickName,
Description = s.Description,
State = (FriendRequestState)s.State
})
;
query = query.OrderByDescending(x => x.Id);
var friendRequestList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
return friendRequestList;
}
#endregion
@@ -154,18 +160,17 @@ namespace IM_API.Services
if (alreadyExists)
throw new BaseException(CodeDefine.FRIEND_REQUEST_EXISTS);
var friendShip = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == dto.FromUserId && x.FriendId == dto.ToUserId);
//检查是否被对方拉黑
bool isBlocked = await _context.Friends.AnyAsync(x =>
x.UserId == dto.FromUserId && x.FriendId == dto.ToUserId && x.Status == (sbyte)FriendStatus.Blocked
);
bool isBlocked = friendShip != null && friendShip.StatusEnum == FriendStatus.Blocked;
if (isBlocked)
throw new BaseException(CodeDefine.FRIEND_REQUEST_REJECTED);
if (friendShip != null)
throw new BaseException(CodeDefine.ALREADY_FRIENDS);
//生成实体
var friendRequst = _mapper.Map<FriendRequest>(dto);
var friend = _mapper.Map<Friend>(friendRequst);
_context.FriendRequests.Add(friendRequst);
_context.Friends.Add(friend);
await _context.SaveChangesAsync();
return true;
}
+69 -14
View File
@@ -1,9 +1,12 @@
using AutoMapper;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
using IM_API.Tools;
using MassTransit;
using Microsoft.EntityFrameworkCore;
namespace IM_API.Services
@@ -13,21 +16,53 @@ namespace IM_API.Services
private readonly ImContext _context;
private readonly ILogger<MessageService> _logger;
private readonly IMapper _mapper;
public MessageService(ImContext context, ILogger<MessageService> logger, IMapper mapper)
//废弃,此处已使用rabbitMQ替代
//private readonly IEventBus _eventBus;
private readonly IPublishEndpoint _endpoint;
public MessageService(
ImContext context, ILogger<MessageService> logger, IMapper mapper, IEventBus eventBus,
IPublishEndpoint publishEndpoint
)
{
_context = context;
_logger = logger;
_mapper = mapper;
//_eventBus = eventBus;
_endpoint = publishEndpoint;
}
public Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc)
public async Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc)
{
throw new NotImplementedException();
}
public Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId, int userBId, int page, int pageSize, bool desc)
{
throw new NotImplementedException();
//获取会话信息,用于获取双方聊天的唯一标识streamkey
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
x => x.Id == conversationId && x.UserId == userId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var query = _context.Messages.AsQueryable();
if(msgId != null)
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id < msgId.Value
)
.OrderByDescending(x => x.Id);
}
else
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id > conversation.LastReadMessageId
);
}
if(pageSize != null)
{
query = query.Take(pageSize.Value);
}
var msgList = await query
.ToListAsync();
msgList = msgList
.OrderBy(x => x.Created)
.ThenBy(t => t.Id)
.ToList();
return _mapper.Map<List<MessageBaseDto>>(msgList);
}
public Task<int> GetUnreadCountAsync(int userId)
@@ -54,21 +89,41 @@ namespace IM_API.Services
{
throw new NotImplementedException();
}
public Task<bool> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
#region
public async Task<MessageBaseDto> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
{
throw new NotImplementedException();
}
//判断群存在
var isExist = await _context.Groups.AnyAsync(x => x.Id == groupId);
if (!isExist) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
//判断是否是群成员
var isMember = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == senderId);
if (!isMember) throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
var message = _mapper.Map<Message>(dto);
message.Sender = senderId;
message.StreamKey = StreamKeyBuilder.Group(
groupId);
_context.Messages.Add(message);
await _context.SaveChangesAsync();
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
return _mapper.Map<MessageBaseDto>(message);
public async Task<bool> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
}
#endregion
#region
public async Task<MessageBaseDto> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
{
bool isExist = await _context.Friends.AnyAsync(x => x.FriendId == receiverId);
if (!isExist) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
var message = _mapper.Map<Message>(dto);
message.Sender = senderId;
//生成消息流唯一标识符
message.StreamKey = StreamKeyBuilder.Private(dto.SenderId, dto.ReceiverId);
_context.Messages.Add(message);
await _context.SaveChangesAsync();
return true;
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
return _mapper.Map<MessageBaseDto>(message);
}
#endregion
}
}
@@ -53,8 +53,8 @@ namespace IM_API.Services
if (json.IsNullOrEmpty) return (false,-1);
try
{
var doc = JsonConvert.DeserializeObject<JsonElement>(json);
var userId = doc.GetProperty("UserId").GetInt32();
using var doc = JsonDocument.Parse(json.ToString());
var userId = doc.RootElement.GetProperty("UserId").GetInt32();
return (true,userId);
}
catch
+3
View File
@@ -102,5 +102,8 @@
/// <summary>后台日志写入失败</summary>
public static CodeDefine OPERATION_LOG_FAILED = new CodeDefine(3004, "操作记录失败");
// 3.9 会话相关错误(3100 ~ 3199
/// <summary>发送时异常</summary>
public static CodeDefine CONVERSATION_NOT_FOUND = new CodeDefine(3100, "会话不存在");
}
}
@@ -0,0 +1,7 @@
namespace IM_API.Tools
{
public class SignalRMethodDefine
{
public static string ReceiveMessage = "ReceiveMessage";
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace IM_API.Tools
{
public static class StreamKeyBuilder
{
public static string Private(long a, long b)
{
if (a <= 0 || b <= 0) throw new ArgumentException();
return $"p:{(a < b ? $"{a}_{b}" : $"{b}_{a}")}";
}
public static string Group(long groupId)
{
if (groupId <= 0) throw new ArgumentException();
return $"g:{groupId}";
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.Text.Json;
namespace IM_API.Tools
{
public class UtcDateTimeConverter : System.Text.Json.Serialization.JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> reader.GetDateTime();
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
// 如果 Kind 已经是 Utc,直接输出。
// 如果不是,先 SpecifyKind 再输出,确保不会发生时区偏移计算
var utcDate = value.Kind == DateTimeKind.Utc ? value : DateTime.SpecifyKind(value, DateTimeKind.Utc);
writer.WriteStringValue(utcDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
}
}
}
+6
View File
@@ -16,5 +16,11 @@
"ConnectionStrings": {
"DefaultConnection": "Server=frp-era.com;Port=26582;Database=IM;User=product;Password=12345678;",
"Redis": "192.168.5.100:6379"
},
"RabbitMQOptions": {
"Host": "192.168.5.100",
"Port": 5672,
"Username": "test",
"Password": "123456"
}
}
+9
View File
@@ -71,3 +71,12 @@ public WeatherForecastController(IDemo demo)
## 4. 模型类字段使用规范
### 4.1 类中状态相关字段,例如:Status,返回值为sbyte。若类中有同名字段+后缀Enum,则优先使用后者,StatusEnum。
## 5.数据库相关
### 5.1 若数据库表结构更新,请在软件包控制台执行如下命令:
```cmd
Scaffold-DbContext "Name=ConnectionStrings:DefaultConnection" Pomelo.EntityFrameworkCore.MySql -OutputDir Models -Context ImContext -Force -NoOnConfiguring
```
+45
View File
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+36
View File
@@ -0,0 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: android
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: ios
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: web
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+16
View File
@@ -0,0 +1,16 @@
# app
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
+28
View File
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+14
View File
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
+44
View File
@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package com.example.app
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+27
View File
@@ -0,0 +1,27 @@
allprojects {
repositories {
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/public") }
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
# ??????????
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14-all.zip
+28
View File
@@ -0,0 +1,28 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/public") }
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")
+34
View File
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
+1
View File
@@ -0,0 +1 @@
#include "Generated.xcconfig"
@@ -0,0 +1 @@
#include "Generated.xcconfig"
@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

Some files were not shown because too many files have changed in this diff Show More