diff --git a/Util.sln b/Util.sln index 96dfd5c38..a4842aabe 100644 --- a/Util.sln +++ b/Util.sln @@ -21,7 +21,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Core.Tests", "test\Uti EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E6328DC1-C8F0-4DC6-941F-C6CAFFF783DF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Ui", "14-Ui", "{354B3720-D050-487D-A46A-22BCFEE56D43}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Ui", "15-Ui", "{354B3720-D050-487D-A46A-22BCFEE56D43}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Ui", "src\Util.Ui\01-Util.Ui.csproj", "{5F7ACBE4-A1D8-4680-AA86-B87F8494BAC2}" EndProject @@ -31,7 +31,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Ui.NgZorro.Tests", "te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01-Core", "01-Core", "{9BFB5DBE-8F64-45A9-BDB2-BAD91D44A4C5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Ui", "15-Ui", "{DE39BB20-AE82-47C3-9141-8CFD4C673217}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Ui", "16-Ui", "{DE39BB20-AE82-47C3-9141-8CFD4C673217}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Ui.Tests", "test\Util.Ui.Tests\Util.Ui.Tests.csproj", "{766CAB38-F064-419F-837B-D6DA8B61AF66}" EndProject @@ -49,7 +49,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Aop.AspectCore", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Aop.AspectCore.Tests", "test\Util.Aop.AspectCore.Tests\Util.Aop.AspectCore.Tests.csproj", "{B8825802-81F7-4671-82E8-3A6E3A21F570}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Domain", "10-Domain", "{7AB71D86-D351-4009-A789-24097897E74A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Domain", "11-Domain", "{7AB71D86-D351-4009-A789-24097897E74A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "04-Util.Validation", "src\Util.Validation\04-Util.Validation.csproj", "{1DE277C2-EE56-4F4D-BB89-71107DE0E4C6}" EndProject @@ -57,15 +57,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Validation.Tests", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Domain", "src\Util.Domain\01-Util.Domain.csproj", "{61982C8D-5376-45EE-BCC3-D4F9E90AF3B0}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "09-Domain", "09-Domain", "{FC5E00D1-ABA5-4168-8B8F-FA2A26D84068}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Domain", "10-Domain", "{FC5E00D1-ABA5-4168-8B8F-FA2A26D84068}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Domain.Tests", "test\Util.Domain.Tests\Util.Domain.Tests.csproj", "{7CA38535-297B-4DA9-AFF5-3199268EEAF6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Events", "src\Util.Events\02-Util.Events.csproj", "{EE426441-DE33-4F3B-B66A-E258AC725B78}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Data", "11-Data", "{66A85767-2877-4B99-84F1-EEABC6DC5247}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Data", "12-Data", "{66A85767-2877-4B99-84F1-EEABC6DC5247}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Data", "12-Data", "{CC492CA2-5D86-4DB8-AB15-B118DF27C344}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Data", "13-Data", "{CC492CA2-5D86-4DB8-AB15-B118DF27C344}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Data.Tests", "test\Util.Data.Tests\Util.Data.Tests.csproj", "{4D40321F-2C3F-4ACB-8AE0-7A228DF0551B}" EndProject @@ -99,11 +99,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "05-Util.Data.Dapper.All", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Domain.Biz", "src\Util.Domain.Biz\02-Util.Domain.Biz.csproj", "{AFB629C5-00C2-492C-8787-C66D810A24DB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Application", "12-Application", "{648CE3E3-CEC4-4F91-8696-6A3FB2F91220}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Application", "13-Application", "{648CE3E3-CEC4-4F91-8696-6A3FB2F91220}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Application", "src\Util.Application\01-Util.Application.csproj", "{9404B5FE-3431-4EC0-BCC6-42525FC5D1B8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Application", "13-Application", "{BB24822F-F104-4D8E-9CB5-0A94DEF04BAB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Application", "14-Application", "{BB24822F-F104-4D8E-9CB5-0A94DEF04BAB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Application.Tests", "test\Util.Application.Tests\Util.Application.Tests.csproj", "{EF7BDA1B-22E7-4F3D-97E0-16C9C10AA1B0}" EndProject @@ -139,7 +139,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Logging.Serilog", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Logging.Serilog.Exceptionless", "src\Util.Logging.Serilog.Exceptionless\03-Util.Logging.Serilog.Exceptionless.csproj", "{9960FA8A-98C1-45FB-8A09-33CA183AA140}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Share", "10-Share", "{E3414144-C19E-40C0-A73A-2FD41AE514D1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Share", "11-Share", "{E3414144-C19E-40C0-A73A-2FD41AE514D1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.TestShare", "test\Util.TestShare\Util.TestShare.csproj", "{1855CAB7-6379-47A4-A0A4-F890C6C46403}" EndProject @@ -219,7 +219,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.AspNetCore.Tests.Integ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "06-Scheduling", "06-Scheduling", "{76AA6040-AA3A-43DD-BD4B-7F931271BFD6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Scheduling", "11-Scheduling", "{6493D592-7930-4979-A07D-B7CAA751C7BE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Scheduling", "12-Scheduling", "{6493D592-7930-4979-A07D-B7CAA751C7BE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Scheduling.Quartz", "src\Util.Scheduling.Quartz\02-Util.Scheduling.Quartz.csproj", "{4E12B03E-D607-492E-BB28-96235993A39F}" EndProject @@ -257,13 +257,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Images.Avatar", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Images.Avatar.Tests.Integration", "test\Util.Images.Avatar.Tests.Integration\Util.Images.Avatar.Tests.Integration.csproj", "{75A31AAB-B7E6-4F13-9B7A-B74216B2EDDE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Generators", "15-Generators", "{411247A4-4665-4BE6-8E5C-08F6D0BA2213}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Generators", "16-Generators", "{411247A4-4665-4BE6-8E5C-08F6D0BA2213}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Generators", "src\Util.Generators\01-Util.Generators.csproj", "{36990D32-6F41-44A6-BB88-A3F15E12C35B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Generators.Razor", "src\Util.Generators.Razor\02-Util.Generators.Razor.csproj", "{21831A4F-B3C9-40E7-855B-75F9D679FF55}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Generators", "16-Generators", "{F7D4E7CB-468D-4570-8D82-D065F08BE40D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "17-Generators", "17-Generators", "{F7D4E7CB-468D-4570-8D82-D065F08BE40D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Generators.Tests", "test\Util.Generators.Tests\Util.Generators.Tests.csproj", "{5996F536-14B9-4F95-A18B-716E5F7EE478}" EndProject @@ -285,11 +285,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Generators.Razor.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Events.Tests.Integration", "test\Util.Events.Tests.Integration\Util.Events.Tests.Integration.csproj", "{FB11256C-06C0-4B24-863A-C87D78F8EE2A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Microservices", "14-Microservices", "{547FD391-A56B-47A6-B20C-5FC04FA15C56}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Microservices", "15-Microservices", "{547FD391-A56B-47A6-B20C-5FC04FA15C56}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Events.Abstractions", "src\Util.Events.Abstractions\01-Util.Events.Abstractions.csproj", "{6233BF36-9636-43BD-9462-2D31A05EE4ED}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Microservices", "13-Microservices", "{1DEC52C8-6692-4DA7-AE34-B76D728F895A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Microservices", "14-Microservices", "{1DEC52C8-6692-4DA7-AE34-B76D728F895A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Microservices", "src\Util.Microservices\01-Util.Microservices.csproj", "{F8417623-6936-43C4-A967-9D6BD002AD16}" EndProject @@ -305,7 +305,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "04-Util.Microservices.Healt EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.WebApiSample", "test\Util.Microservices.Dapr.WebApiSample\Util.Microservices.Dapr.WebApiSample.csproj", "{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.PubsubSample", "test\Util.Microservices.Dapr.PubsubSample\Util.Microservices.Dapr.PubsubSample.csproj", "{BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.PubsubSample", "test\Util.Microservices.Dapr.PubsubSample\Util.Microservices.Dapr.PubsubSample.csproj", "{5D74BD1D-3142-4478-9062-4543FADA3E91}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.Tests", "test\Util.Microservices.Dapr.Tests\Util.Microservices.Dapr.Tests.csproj", "{B737883C-9450-46CB-B81C-1A3405071D04}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Tenants", "src\Util.Tenants\02-Util.Tenants.csproj", "{156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Tenants.Tests", "test\Util.Tenants.Tests\Util.Tenants.Tests.csproj", "{DE51B662-C843-4E99-87F8-F367504D5640}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Tenant", "10-Tenant", "{98F1FEAD-AFD5-4744-8A55-C57920461EB9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "09-Tenant", "09-Tenant", "{835A8155-411B-4DF2-AE58-D3FDEFB61E63}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Tenants.Abstractions", "src\Util.Tenants.Abstractions\01-Util.Tenants.Abstractions.csproj", "{0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Events.MediatR", "src\Util.Events.MediatR\03-Util.Events.MediatR.csproj", "{FA6AF0F1-B4EE-4E46-B933-9573861A10FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Events.MediatR.Tests.Integration", "test\Util.Events.MediatR.Tests.Integration\Util.Events.MediatR.Tests.Integration.csproj", "{4FECE655-7B06-4DED-BED0-B67731A43E0B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Tenants.Tests.Integration", "test\Util.Tenants.Tests.Integration\Util.Tenants.Tests.Integration.csproj", "{6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -749,10 +767,38 @@ Global {AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Release|Any CPU.Build.0 = Release|Any CPU - {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Release|Any CPU.Build.0 = Release|Any CPU + {5D74BD1D-3142-4478-9062-4543FADA3E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D74BD1D-3142-4478-9062-4543FADA3E91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D74BD1D-3142-4478-9062-4543FADA3E91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D74BD1D-3142-4478-9062-4543FADA3E91}.Release|Any CPU.Build.0 = Release|Any CPU + {B737883C-9450-46CB-B81C-1A3405071D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B737883C-9450-46CB-B81C-1A3405071D04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B737883C-9450-46CB-B81C-1A3405071D04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B737883C-9450-46CB-B81C-1A3405071D04}.Release|Any CPU.Build.0 = Release|Any CPU + {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Release|Any CPU.Build.0 = Release|Any CPU + {DE51B662-C843-4E99-87F8-F367504D5640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE51B662-C843-4E99-87F8-F367504D5640}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE51B662-C843-4E99-87F8-F367504D5640}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE51B662-C843-4E99-87F8-F367504D5640}.Release|Any CPU.Build.0 = Release|Any CPU + {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Release|Any CPU.Build.0 = Release|Any CPU + {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Release|Any CPU.Build.0 = Release|Any CPU + {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Release|Any CPU.Build.0 = Release|Any CPU + {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -904,7 +950,16 @@ Global {DF1E55A5-7EA8-41B2-BEBA-72544D256069} = {547FD391-A56B-47A6-B20C-5FC04FA15C56} {9C58E50F-0589-4A4E-B94D-2EC0BF02F61F} = {1DEC52C8-6692-4DA7-AE34-B76D728F895A} {AE19ED93-AFC4-4F91-93BD-B00D343D3BA0} = {547FD391-A56B-47A6-B20C-5FC04FA15C56} - {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86} = {547FD391-A56B-47A6-B20C-5FC04FA15C56} + {5D74BD1D-3142-4478-9062-4543FADA3E91} = {547FD391-A56B-47A6-B20C-5FC04FA15C56} + {B737883C-9450-46CB-B81C-1A3405071D04} = {547FD391-A56B-47A6-B20C-5FC04FA15C56} + {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6} = {98F1FEAD-AFD5-4744-8A55-C57920461EB9} + {DE51B662-C843-4E99-87F8-F367504D5640} = {835A8155-411B-4DF2-AE58-D3FDEFB61E63} + {98F1FEAD-AFD5-4744-8A55-C57920461EB9} = {C01B9930-D67F-41C5-90C9-C87DC53F39CB} + {835A8155-411B-4DF2-AE58-D3FDEFB61E63} = {E6328DC1-C8F0-4DC6-941F-C6CAFFF783DF} + {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87} = {98F1FEAD-AFD5-4744-8A55-C57920461EB9} + {FA6AF0F1-B4EE-4E46-B933-9573861A10FC} = {59F82491-51E3-4A12-B427-9815AEA23D10} + {4FECE655-7B06-4DED-BED0-B67731A43E0B} = {3ABD7B4B-37F9-4D49-92D9-384190177286} + {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC} = {835A8155-411B-4DF2-AE58-D3FDEFB61E63} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {94347832-A36D-4C42-9C4D-B848BD4F5DA9} diff --git a/build/version.props b/build/version.props index ff0bd05be..c95ef3448 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 7 1 - 20 + 22 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/src/Util.Aop.AspectCore/IgnoreAttribute.cs b/src/Util.Aop.AspectCore/IgnoreAttribute.cs index d08cf4114..0032d80e7 100644 --- a/src/Util.Aop.AspectCore/IgnoreAttribute.cs +++ b/src/Util.Aop.AspectCore/IgnoreAttribute.cs @@ -3,5 +3,5 @@ /// /// 忽略拦截 /// -public class IgnoreAttribute : AspectCore.DynamicProxy.NonAspectAttribute { +public class IgnoreAttribute : NonAspectAttribute { } \ No newline at end of file diff --git a/src/Util.AspNetCore/Helpers/Web.cs b/src/Util.AspNetCore/Helpers/Web.cs index 84c3abef6..78cf379f2 100644 --- a/src/Util.AspNetCore/Helpers/Web.cs +++ b/src/Util.AspNetCore/Helpers/Web.cs @@ -343,4 +343,60 @@ public static async Task DownloadAsync( byte[] bytes, string fileName, Encoding } #endregion + + #region GetCookie(获取Cookie) + + /// + /// 获取Cookie + /// + /// cookie键名 + public static string GetCookie( string key ) { + return Request?.Cookies[key]; + } + + #endregion + + #region SetCookie(设置Cookie) + + /// + /// 设置Cookie + /// + /// cookie键名 + /// 值 + public static void SetCookie( string key, string value ) { + Response?.Cookies.Append( key, value ); + } + + /// + /// 设置Cookie + /// + /// cookie键名 + /// 值 + /// Cookie配置 + public static void SetCookie( string key,string value,CookieOptions options ) { + Response?.Cookies.Append( key, value, options ); + } + + #endregion + + #region RemoveCookie(移除Cookie) + + /// + /// 移除Cookie + /// + /// cookie键名 + public static void RemoveCookie( string key ) { + Response?.Cookies.Delete( key ); + } + + /// + /// 移除Cookie + /// + /// cookie键名 + /// Cookie配置 + public static void RemoveCookie( string key, CookieOptions options ) { + Response?.Cookies.Delete( key, options ); + } + + #endregion } \ No newline at end of file diff --git a/src/Util.AspNetCore/Http/HttpRequest.cs b/src/Util.AspNetCore/Http/HttpRequest.cs index 44045ebb4..bf16d337d 100644 --- a/src/Util.AspNetCore/Http/HttpRequest.cs +++ b/src/Util.AspNetCore/Http/HttpRequest.cs @@ -1,4 +1,5 @@ -using System.Text.Encodings.Web; +using Microsoft.Net.Http.Headers; +using System.Text.Encodings.Web; using System.Text.Unicode; using Util.Helpers; using Util.SystemTextJson; @@ -61,6 +62,7 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient, HeaderParams = new Dictionary(); QueryParams = new Dictionary(); Params = new Dictionary(); + Cookies = new Dictionary(); HttpContentType = Util.Http.HttpContentType.Json.Description(); CharacterEncoding = System.Text.Encoding.UTF8; } @@ -94,6 +96,10 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient, /// protected IDictionary HeaderParams { get; } /// + /// Cookie参数集合 + /// + protected IDictionary Cookies { get; } + /// /// 查询字符串参数集合 /// protected IDictionary QueryParams { get; } @@ -106,6 +112,10 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient, /// protected object Param { get; private set; } /// + /// 是否自动携带cookie + /// + protected bool? IsUseCookies { get; private set; } + /// /// 发送前操作 /// protected Func SendBeforeAction { get; private set; } @@ -299,6 +309,39 @@ public IHttpRequest QueryString( object queryString ) { #endregion + #region UseCookies(设置是否自动携带cookie) + + /// + public IHttpRequest UseCookies( bool isUseCookies = true ) { + IsUseCookies = isUseCookies; + return this; + } + + #endregion + + #region Cookie(设置Cookie) + + /// + public IHttpRequest Cookie( string key, string value ) { + if ( key.IsEmpty() ) + return this; + if ( Cookies.ContainsKey( key ) ) + Cookies.Remove( key ); + Cookies.Add( key, value ); + return this; + } + + /// + public IHttpRequest Cookie( IDictionary cookies ) { + if ( Cookies == null ) + return this; + foreach ( var cookie in Cookies ) + Cookie( cookie.Key, cookie.Value ); + return this; + } + + #endregion + #region Content(添加内容参数) /// @@ -439,6 +482,7 @@ public IHttpRequest OnComplete( Action act /// protected virtual HttpRequestMessage CreateMessage() { var message = new HttpRequestMessage( _httpMethod, GetUrl( _url ) ); + AddCookies(); AddHeaders( message ); message.Content = CreateHttpContent(); return message; @@ -452,6 +496,18 @@ protected virtual string GetUrl( string url ) { return QueryHelpers.AddQueryString( url, QueryParams ); } + /// + /// 添加Cookie + /// + protected virtual void AddCookies() { + if ( Cookies.Count == 0 ) + return; + var cookieValues = new List(); + foreach ( var cookie in Cookies ) + cookieValues.Add( new CookieHeaderValue( cookie.Key, cookie.Value ) ); + Header( "Cookie", cookieValues.Select( t => t.ToString() ).Join() ); + } + /// /// 添加请求头 /// @@ -582,6 +638,7 @@ protected HttpClient GetClient() { return _httpClient; var clientHandler = CreateHttpClientHandler(); InitHttpClientHandler( clientHandler ); + InitUseCookies( clientHandler ); return _httpClientName.IsEmpty() ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient( _httpClientName ); } @@ -624,6 +681,18 @@ protected virtual void InitCertificate( HttpClientHandler handler ) { #endregion + #region InitUseCookies(初始化是否携带Cookie) + + /// + /// 初始化是否携带Cookie + /// + protected virtual void InitUseCookies( HttpClientHandler handler ) { + if ( IsUseCookies == null ) + handler.UseCookies = false; + } + + #endregion + #region InitHttpClient(初始化Http客户端) /// diff --git a/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs b/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs index 8937c661f..d30dc9146 100644 --- a/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs +++ b/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs @@ -1,6 +1,5 @@ using Util.Helpers; using Util.Http; -using Util.Sessions; using Util.SystemTextJson; namespace Util.Infrastructure; @@ -33,7 +32,6 @@ public Action Register( ServiceContext serviceContext ) { RegisterHttpContextAccessor( services ); services.AddHttpClient(); RegisterServiceLocator(); - RegisterSession( services ); RegisterHttpClient( services ); ConfigJsonOptions( services ); } ); @@ -56,13 +54,6 @@ private void RegisterServiceLocator() { Ioc.SetServiceProviderAction( () => Web.ServiceProvider ); } - /// - /// 注册用户会话 - /// - private void RegisterSession( IServiceCollection services ) { - services.TryAddSingleton(); - } - /// /// 注册Http客户端 /// diff --git a/src/Util.AspNetCore/Sessions/UserSession.cs b/src/Util.AspNetCore/Sessions/UserSession.cs index 2ad2c7391..c5bacbbd4 100644 --- a/src/Util.AspNetCore/Sessions/UserSession.cs +++ b/src/Util.AspNetCore/Sessions/UserSession.cs @@ -1,5 +1,4 @@ using Util.Helpers; -using Util.Security; namespace Util.Sessions; @@ -13,22 +12,24 @@ public class UserSession : ISession { public static readonly ISession Null = NullSession.Instance; /// - /// 用户会话 + /// 用户会话实例 /// public static readonly ISession Instance = new UserSession(); - /// - /// 是否认证 - /// - public bool IsAuthenticated => Web.Identity.IsAuthenticated; + /// + public virtual IServiceProvider ServiceProvider => Web.ServiceProvider; - /// - /// 用户标识 - /// - public string UserId { + /// + public virtual bool IsAuthenticated => Web.Identity.IsAuthenticated; + + /// + public virtual string UserId { get { var result = Web.Identity.GetValue( ClaimTypes.UserId ); return result.IsEmpty() ? Web.Identity.GetValue( System.Security.Claims.ClaimTypes.NameIdentifier ) : result; } } + + /// + public virtual string TenantId => Web.Identity.GetValue( ClaimTypes.TenantId ); } \ No newline at end of file diff --git a/src/Util.AspNetCore/Usings.cs b/src/Util.AspNetCore/Usings.cs index f988abb31..34a9bb72f 100644 --- a/src/Util.AspNetCore/Usings.cs +++ b/src/Util.AspNetCore/Usings.cs @@ -27,4 +27,5 @@ global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.DataProtection; global using Microsoft.AspNetCore.Builder; -global using Util.Dependency; \ No newline at end of file +global using Util.Dependency; +global using Util.Security; \ No newline at end of file diff --git a/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj b/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj index 25ec771d7..9489ba848 100644 --- a/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj +++ b/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj @@ -27,9 +27,9 @@ - - - + + + diff --git a/src/Util.Caching/NullCache.cs b/src/Util.Caching/NullCache.cs new file mode 100644 index 000000000..0d65a2f44 --- /dev/null +++ b/src/Util.Caching/NullCache.cs @@ -0,0 +1,230 @@ +namespace Util.Caching; + +/// +/// 空缓存 +/// +public class NullCache : ILocalCache { + /// + /// 缓存空实例 + /// + public static readonly ILocalCache Instance = new NullCache(); + + /// + public bool Exists( CacheKey key ) { + return false; + } + + /// + public bool Exists( string key ) { + return false; + } + + /// + public Task ExistsAsync( CacheKey key, CancellationToken cancellationToken = default ) { + return Task.FromResult( false ); + } + + /// + public Task ExistsAsync( string key, CancellationToken cancellationToken = default ) { + return Task.FromResult( false ); + } + + /// + public T Get( CacheKey key ) { + return default; + } + + /// + public T Get( string key ) { + return default; + } + + /// + public List Get( IEnumerable keys ) { + return new List(); + } + + /// + public List Get( IEnumerable keys ) { + return new List(); + } + + /// + public T Get( CacheKey key, Func action, CacheOptions options = null ) { + return action == null ? default : action(); + } + + /// + public T Get( string key, Func action, CacheOptions options = null ) { + return action == null ? default : action(); + } + + /// + public Task GetAsync( string key, Type type, CancellationToken cancellationToken = default ) { + return null; + } + + /// + public async Task GetAsync( CacheKey key, CancellationToken cancellationToken = default ) { + await Task.CompletedTask; + return default; + } + + /// + public async Task GetAsync( string key, CancellationToken cancellationToken = default ) { + await Task.CompletedTask; + return default; + } + + /// + public async Task> GetAsync( IEnumerable keys, CancellationToken cancellationToken = default ) { + await Task.CompletedTask; + return new List(); + } + + /// + public async Task> GetAsync( IEnumerable keys, CancellationToken cancellationToken = default ) { + await Task.CompletedTask; + return new List(); + } + + /// + public async Task GetAsync( CacheKey key, Func> action, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return action == null ? default : await action(); + } + + /// + public async Task GetAsync( string key, Func> action, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return action == null ? default : await action(); + } + + /// + public List GetByPrefix( string prefix ) { + return new List(); + } + + /// + public async Task> GetByPrefixAsync( string prefix, CancellationToken cancellationToken = default ) { + await Task.CompletedTask; + return new List(); + } + + /// + public bool TrySet( CacheKey key, T value, CacheOptions options = null ) { + return false; + } + + /// + public bool TrySet( string key, T value, CacheOptions options = null ) { + return false; + } + + /// + public Task TrySetAsync( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.FromResult( false ); + } + + /// + public Task TrySetAsync( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.FromResult( false ); + } + + /// + public void Set( CacheKey key, T value, CacheOptions options = null ) { + } + + /// + public void Set( string key, T value, CacheOptions options = null ) { + } + + /// + public void Set( IDictionary items, CacheOptions options = null ) { + } + + /// + public void Set( IDictionary items, CacheOptions options = null ) { + } + + /// + public Task SetAsync( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task SetAsync( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task SetAsync( IDictionary items, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task SetAsync( IDictionary items, CacheOptions options = null, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public void Remove( CacheKey key ) { + } + + /// + public void Remove( string key ) { + } + + /// + public void Remove( IEnumerable keys ) { + } + + /// + public void Remove( IEnumerable keys ) { + } + + /// + public Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task RemoveAsync( string key, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task RemoveAsync( IEnumerable keys, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public Task RemoveAsync( IEnumerable keys, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public void RemoveByPrefix( string prefix ) { + } + + /// + public Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public void RemoveByPattern( string pattern ) { + } + + /// + public Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } + + /// + public void Clear() { + } + + /// + public Task ClearAsync( CancellationToken cancellationToken = default ) { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Util.Core/Dependency/Container.cs b/src/Util.Core/Dependency/Container.cs index 31367240d..7929ec116 100644 --- a/src/Util.Core/Dependency/Container.cs +++ b/src/Util.Core/Dependency/Container.cs @@ -16,7 +16,7 @@ public class Container { /// /// 容器实例 /// - public static readonly Container Instance = new Container(); + public static readonly Container Instance = new(); /// /// 初始化容器 @@ -67,4 +67,12 @@ public object GetService( Type type ) { var provider = GetServiceProvider(); return provider.GetService( type ); } + + /// + /// 清理 + /// + public void Clear() { + _services.Clear(); + _provider = null; + } } \ No newline at end of file diff --git a/src/Util.Core/DictionaryExtensions.cs b/src/Util.Core/DictionaryExtensions.cs new file mode 100644 index 000000000..482449d41 --- /dev/null +++ b/src/Util.Core/DictionaryExtensions.cs @@ -0,0 +1,17 @@ +namespace Util; + +/// +/// 字典操作扩展 +/// +public static class DictionaryExtensions { + /// + /// 获取值 + /// + /// 字典数据 + /// 键 + public static TValue GetValue( this IDictionary source, TKey key ) { + if ( source == null ) + return default; + return source.TryGetValue( key, out var obj ) ? obj : default; + } +} \ No newline at end of file diff --git a/src/Util.Core/Helpers/Ioc.cs b/src/Util.Core/Helpers/Ioc.cs index b8a3c1b1f..e093eb109 100644 --- a/src/Util.Core/Helpers/Ioc.cs +++ b/src/Util.Core/Helpers/Ioc.cs @@ -18,6 +18,13 @@ public static class Ioc { /// public static IServiceScopeFactory ServiceScopeFactory { get; set; } + /// + /// 创建新容器 + /// + public static Util.Dependency.Container CreateContainer() { + return new Util.Dependency.Container(); + } + /// /// 获取服务集合 /// @@ -36,7 +43,7 @@ public static void SetServiceProviderAction( Func action ) { /// /// 获取 /// - private static IServiceProvider GetServiceProvider() { + public static IServiceProvider GetServiceProvider() { var provider = _getServiceProviderAction?.Invoke(); if ( provider != null ) return provider; @@ -102,4 +109,11 @@ public static IServiceScope CreateScope() { var provider = GetServiceProvider(); return provider.CreateScope(); } + + /// + /// 清理 + /// + public static void Clear() { + _container.Clear(); + } } \ No newline at end of file diff --git a/src/Util.Core/Helpers/String.cs b/src/Util.Core/Helpers/String.cs index 7f9828f24..989cdee1c 100644 --- a/src/Util.Core/Helpers/String.cs +++ b/src/Util.Core/Helpers/String.cs @@ -1,4 +1,4 @@ -namespace Util.Helpers; +namespace Util.Helpers; /// /// 字符串操作 @@ -15,10 +15,10 @@ public static class String { /// 引号,默认不带引号,范例:单引号 "'" /// 分隔符,默认使用逗号分隔 public static string Join( IEnumerable values, string quotes = "", string separator = "," ) { - if( values == null ) + if ( values == null ) return string.Empty; var result = new StringBuilder(); - foreach( var each in values ) + foreach ( var each in values ) result.AppendFormat( "{0}{1}{0}{2}", quotes, each, separator ); return result.ToString().RemoveEnd( separator ); } @@ -32,10 +32,10 @@ public static string Join( IEnumerable values, string quotes = "", string /// /// 值 public static string FirstLowerCase( string value ) { - if( string.IsNullOrWhiteSpace( value ) ) + if ( string.IsNullOrWhiteSpace( value ) ) return string.Empty; var result = Rune.DecodeFromUtf16( value, out var rune, out var charsConsumed ); - if( result != OperationStatus.Done || Rune.IsLower( rune ) ) + if ( result != OperationStatus.Done || Rune.IsLower( rune ) ) return value; return Rune.ToLowerInvariant( rune ) + value[charsConsumed..]; } @@ -49,10 +49,10 @@ public static string FirstLowerCase( string value ) { /// /// 值 public static string FirstUpperCase( string value ) { - if( string.IsNullOrWhiteSpace( value ) ) + if ( string.IsNullOrWhiteSpace( value ) ) return string.Empty; var result = Rune.DecodeFromUtf16( value, out var rune, out var charsConsumed ); - if( result != OperationStatus.Done || Rune.IsUpper( rune ) ) + if ( result != OperationStatus.Done || Rune.IsUpper( rune ) ) return value; return Rune.ToUpperInvariant( rune ) + value[charsConsumed..]; } @@ -67,9 +67,9 @@ public static string FirstUpperCase( string value ) { /// 值 /// 要移除的值 public static string RemoveStart( string value, string start ) { - if( string.IsNullOrWhiteSpace( value ) ) + if ( string.IsNullOrWhiteSpace( value ) ) return string.Empty; - if( string.IsNullOrEmpty( start ) ) + if ( string.IsNullOrEmpty( start ) ) return value; if ( value.StartsWith( start, StringComparison.Ordinal ) == false ) return value; @@ -82,15 +82,15 @@ public static string RemoveStart( string value, string start ) { /// 值 /// 要移除的值 public static StringBuilder RemoveStart( StringBuilder value, string start ) { - if( value == null || value.Length == 0 ) + if ( value == null || value.Length == 0 ) return null; - if( string.IsNullOrEmpty( start ) ) + if ( string.IsNullOrEmpty( start ) ) return value; - if( start.Length > value.Length ) + if ( start.Length > value.Length ) return value; var chars = start.ToCharArray(); - for( int i = 0; i < chars.Length; i++ ) { - if( value[i] != chars[i] ) + for ( int i = 0; i < chars.Length; i++ ) { + if ( value[i] != chars[i] ) return value; } return value.Remove( 0, start.Length ); @@ -106,11 +106,11 @@ public static StringBuilder RemoveStart( StringBuilder value, string start ) { /// 值 /// 要移除的值 public static string RemoveEnd( string value, string end ) { - if( string.IsNullOrWhiteSpace( value ) ) + if ( string.IsNullOrWhiteSpace( value ) ) return string.Empty; - if( string.IsNullOrEmpty( end ) ) + if ( string.IsNullOrEmpty( end ) ) return value; - if( value.EndsWith( end, StringComparison.Ordinal ) == false ) + if ( value.EndsWith( end, StringComparison.Ordinal ) == false ) return value; return value.Substring( 0, value.LastIndexOf( end, StringComparison.Ordinal ) ); } @@ -121,16 +121,16 @@ public static string RemoveEnd( string value, string end ) { /// 值 /// 要移除的值 public static StringBuilder RemoveEnd( StringBuilder value, string end ) { - if( value == null || value.Length == 0 ) + if ( value == null || value.Length == 0 ) return null; - if( string.IsNullOrEmpty( end ) ) + if ( string.IsNullOrEmpty( end ) ) return value; - if( end.Length > value.Length ) + if ( end.Length > value.Length ) return value; var chars = end.ToCharArray(); - for( int i = chars.Length-1; i >= 0; i-- ) { + for ( int i = chars.Length - 1; i >= 0; i-- ) { var j = value.Length - ( chars.Length - i ); - if( value[j] != chars[i] ) + if ( value[j] != chars[i] ) return value; } return value.Remove( value.Length - end.Length, end.Length ); @@ -145,10 +145,10 @@ public static StringBuilder RemoveEnd( StringBuilder value, string end ) { /// /// 汉字文本,范例: 中国 public static string PinYin( string chineseText ) { - if( chineseText.IsEmpty() ) + if ( chineseText.IsEmpty() ) return string.Empty; var result = new StringBuilder(); - foreach( char text in chineseText ) + foreach ( char text in chineseText ) result.Append( ResolvePinYin( text ) ); return result.ToString().ToLower(); } @@ -158,11 +158,11 @@ public static string PinYin( string chineseText ) { /// private static string ResolvePinYin( char text ) { byte[] charBytes = Encoding.UTF8.GetBytes( text.ToString() ); - if( charBytes[0] <= 127 ) + if ( charBytes[0] <= 127 ) return text.ToString(); var unicode = (ushort)( charBytes[0] * 256 + charBytes[1] ); string pinYin = ResolveByCode( unicode ); - if( pinYin.IsEmpty() == false ) + if ( pinYin.IsEmpty() == false ) return pinYin; return ResolveByConst( text.ToString() ); } @@ -171,51 +171,51 @@ private static string ResolvePinYin( char text ) { /// 使用字符编码方式获取拼音简码 /// private static string ResolveByCode( ushort unicode ) { - if( unicode >= '\uB0A1' && unicode <= '\uB0C4' ) + if ( unicode >= '\uB0A1' && unicode <= '\uB0C4' ) return "A"; - if( unicode >= '\uB0C5' && unicode <= '\uB2C0' && unicode != 45464 ) + if ( unicode >= '\uB0C5' && unicode <= '\uB2C0' && unicode != 45464 ) return "B"; - if( unicode >= '\uB2C1' && unicode <= '\uB4ED' ) + if ( unicode >= '\uB2C1' && unicode <= '\uB4ED' ) return "C"; - if( unicode >= '\uB4EE' && unicode <= '\uB6E9' ) + if ( unicode >= '\uB4EE' && unicode <= '\uB6E9' ) return "D"; - if( unicode >= '\uB6EA' && unicode <= '\uB7A1' ) + if ( unicode >= '\uB6EA' && unicode <= '\uB7A1' ) return "E"; - if( unicode >= '\uB7A2' && unicode <= '\uB8C0' ) + if ( unicode >= '\uB7A2' && unicode <= '\uB8C0' ) return "F"; - if( unicode >= '\uB8C1' && unicode <= '\uB9FD' ) + if ( unicode >= '\uB8C1' && unicode <= '\uB9FD' ) return "G"; - if( unicode >= '\uB9FE' && unicode <= '\uBBF6' ) + if ( unicode >= '\uB9FE' && unicode <= '\uBBF6' ) return "H"; - if( unicode >= '\uBBF7' && unicode <= '\uBFA5' ) + if ( unicode >= '\uBBF7' && unicode <= '\uBFA5' ) return "J"; - if( unicode >= '\uBFA6' && unicode <= '\uC0AB' ) + if ( unicode >= '\uBFA6' && unicode <= '\uC0AB' ) return "K"; - if( unicode >= '\uC0AC' && unicode <= '\uC2E7' ) + if ( unicode >= '\uC0AC' && unicode <= '\uC2E7' ) return "L"; - if( unicode >= '\uC2E8' && unicode <= '\uC4C2' ) + if ( unicode >= '\uC2E8' && unicode <= '\uC4C2' ) return "M"; - if( unicode >= '\uC4C3' && unicode <= '\uC5B5' ) + if ( unicode >= '\uC4C3' && unicode <= '\uC5B5' ) return "N"; - if( unicode >= '\uC5B6' && unicode <= '\uC5BD' ) + if ( unicode >= '\uC5B6' && unicode <= '\uC5BD' ) return "O"; - if( unicode >= '\uC5BE' && unicode <= '\uC6D9' ) + if ( unicode >= '\uC5BE' && unicode <= '\uC6D9' ) return "P"; - if( unicode >= '\uC6DA' && unicode <= '\uC8BA' ) + if ( unicode >= '\uC6DA' && unicode <= '\uC8BA' ) return "Q"; - if( unicode >= '\uC8BB' && unicode <= '\uC8F5' ) + if ( unicode >= '\uC8BB' && unicode <= '\uC8F5' ) return "R"; - if( unicode >= '\uC8F6' && unicode <= '\uCBF9' ) + if ( unicode >= '\uC8F6' && unicode <= '\uCBF9' ) return "S"; - if( unicode >= '\uCBFA' && unicode <= '\uCDD9' ) + if ( unicode >= '\uCBFA' && unicode <= '\uCDD9' ) return "T"; - if( unicode >= '\uCDDA' && unicode <= '\uCEF3' ) + if ( unicode >= '\uCDDA' && unicode <= '\uCEF3' ) return "W"; - if( unicode >= '\uCEF4' && unicode <= '\uD188' ) + if ( unicode >= '\uCEF4' && unicode <= '\uD188' ) return "X"; - if( unicode >= '\uD1B9' && unicode <= '\uD4D0' ) + if ( unicode >= '\uD1B9' && unicode <= '\uD4D0' ) return "Y"; - if( unicode >= '\uD4D1' && unicode <= '\uD7F9' ) + if ( unicode >= '\uD4D1' && unicode <= '\uD7F9' ) return "Z"; return string.Empty; } @@ -225,10 +225,107 @@ private static string ResolveByCode( ushort unicode ) { /// private static string ResolveByConst( string text ) { int index = Const.ChinesePinYin.IndexOf( text, StringComparison.Ordinal ); - if( index < 0 ) + if ( index < 0 ) return string.Empty; return Const.ChinesePinYin.Substring( index + 1, 1 ); } #endregion + + #region Extract(提取字符串中的变量值) + + /// + /// 提取字符串中的变量值 + /// + /// 原始值,范例: Hello,World + /// 字符串格式,范例: 原始值为Hello,World,格式为Hello,{value} ,则value变量的值为World + public static IDictionary Extract( string value, string format ) { + var result = new Dictionary(); + if ( value.IsEmpty() ) + return result; + if ( format.IsEmpty() ) + return result; + if ( format.Contains( "{", StringComparison.Ordinal ) == false ) + return result; + if ( format.Contains( "}", StringComparison.Ordinal ) == false ) + return result; + var formatItems = SplitFormat( format.SafeString() ); + return ExtractValue( value.SafeString(), formatItems ); + } + + /// + /// 拆分格式字符串 + /// + private static List SplitFormat( string format ) { + var result = new List(); + var item = new StringBuilder(); + for ( int i = 0; i < format.Length; i++ ) { + var temp = format[i]; + if ( temp == '{' ) { + item.RemoveEnd( "{" ); + if ( i == 0 ) { + result.Add( string.Empty ); + } + if ( item.Length > 0 ) { + result.Add( item.ToString() ); + item.Clear(); + } + item.Append( temp ); + continue; + } + if ( temp == '}' ) { + if( item.ToString().IsEmpty() ) + continue; + item.RemoveEnd( "}" ); + item.Append( temp ); + result.Add( item.ToString() ); + item.Clear(); + if ( i == format.Length - 1 ) + result.Add( string.Empty ); + continue; + } + item.Append( temp ); + if ( i == format.Length - 1 ) { + result.Add( item.ToString() ); + item.Clear(); + } + } + return result; + } + + /// + /// 提取字符串中变量值 + /// + private static IDictionary ExtractValue( string value, List formatItems ) { + var result = new Dictionary(); + var leftIndex = 0; + var length = 0; + for ( int i = 0; i < formatItems.Count; i++ ) { + var item = formatItems[i]; + if ( item == string.Empty ) + continue; + if ( item.StartsWith( "{", StringComparison.Ordinal ) == false ) { + leftIndex += item.Length; + continue; + } + if ( i + 1 < formatItems.Count ) { + var rightItem = formatItems[i + 1]; + if ( rightItem == string.Empty ) + length = value.Length - leftIndex; + else + length = value.IndexOf( rightItem, leftIndex + 1, StringComparison.OrdinalIgnoreCase ) - leftIndex; + } + var varName = item.Replace( "{", "" ).Replace( "}", "" ); + if ( length <= 0 ) { + result.Add( varName, string.Empty ); + continue; + } + var variableValue = value.Substring( leftIndex, length ); + result.Add( varName, variableValue ); + leftIndex += length; + } + return result; + } + + #endregion } \ No newline at end of file diff --git a/src/Util.Core/Http/IHttpRequest.cs b/src/Util.Core/Http/IHttpRequest.cs index 563ed7343..2c8a5fdca 100644 --- a/src/Util.Core/Http/IHttpRequest.cs +++ b/src/Util.Core/Http/IHttpRequest.cs @@ -85,6 +85,22 @@ public interface IHttpRequest : IHttpRequest where TResult : class { /// 查询字符串对象 IHttpRequest QueryString( object queryString ); /// + /// 设置是否自动携带cookie + /// + /// 是否自动携带cookie + IHttpRequest UseCookies( bool isUseCookies = true ); + /// + /// 设置Cookie + /// + /// 键 + /// 值 + IHttpRequest Cookie( string key, string value ); + /// + /// 设置Cookie集合 + /// + /// Cookie集合 + IHttpRequest Cookie( IDictionary cookies ); + /// /// 添加参数,作为请求内容发送 /// /// 键 diff --git a/src/Util.Core/Infrastructure/Bootstrapper.cs b/src/Util.Core/Infrastructure/Bootstrapper.cs index 6efc10464..add89b6d4 100644 --- a/src/Util.Core/Infrastructure/Bootstrapper.cs +++ b/src/Util.Core/Infrastructure/Bootstrapper.cs @@ -18,9 +18,13 @@ public class Bootstrapper { /// private readonly Action _setupAction; /// + /// 程序集查找器 + /// + private readonly IAssemblyFinder _assemblyFinder; + /// /// 类型查找器 /// - private readonly ITypeFinder _finder; + private readonly ITypeFinder _typeFinder; /// /// 服务配置操作列表 /// @@ -34,8 +38,8 @@ public class Bootstrapper { public Bootstrapper( IHostBuilder hostBuilder, Action setupAction = null ) { _hostBuilder = hostBuilder ?? throw new ArgumentNullException( nameof( hostBuilder ) ); _setupAction = setupAction; - var assemblyFinder = new AppDomainAssemblyFinder { AssemblySkipPattern = BootstrapperConfig.AssemblySkipPattern }; - _finder = new AppDomainTypeFinder( assemblyFinder ); + _assemblyFinder = new AppDomainAssemblyFinder { AssemblySkipPattern = BootstrapperConfig.AssemblySkipPattern }; + _typeFinder = new AppDomainTypeFinder( _assemblyFinder ); _serviceActions = new List(); } @@ -56,7 +60,8 @@ public virtual void Start() { protected virtual void SetConfiguration() { _hostBuilder.ConfigureServices( ( context, services ) => { Util.Helpers.Config.SetConfiguration( context.Configuration ); - services.TryAddSingleton( _finder ); + services.TryAddSingleton( _assemblyFinder ); + services.TryAddSingleton( _typeFinder ); } ); } @@ -64,9 +69,9 @@ protected virtual void SetConfiguration() { /// 解析服务注册器 /// protected virtual void ResolveServiceRegistrar() { - var types = _finder.Find(); + var types = _typeFinder.Find(); var instances = types.Select( type => Reflection.CreateInstance( type ) ).Where( t => t.Enabled ).OrderBy( t => t.OrderId ).ToList(); - var context = new ServiceContext( _hostBuilder, _finder ); + var context = new ServiceContext( _hostBuilder, _assemblyFinder, _typeFinder ); instances.ForEach( t => _serviceActions.Add( t.Register( context ) ) ); } @@ -81,7 +86,7 @@ protected virtual void ConfigOptions() { /// 解析依赖注册器 /// protected virtual void ResolveDependencyRegistrar() { - var types = _finder.Find(); + var types = _typeFinder.Find(); var instances = types.Select( type => Reflection.CreateInstance( type ) ).OrderBy( t => t.Order ).ToList(); _hostBuilder.ConfigureServices( ( context, services ) => { instances.ForEach( t => t.Register( services ) ); diff --git a/src/Util.Core/Infrastructure/BootstrapperConfig.cs b/src/Util.Core/Infrastructure/BootstrapperConfig.cs index 2b397d33f..0f870f0a3 100644 --- a/src/Util.Core/Infrastructure/BootstrapperConfig.cs +++ b/src/Util.Core/Infrastructure/BootstrapperConfig.cs @@ -7,5 +7,5 @@ public class BootstrapperConfig { /// /// 启动时扫描程序集的过滤模式 /// - public static string AssemblySkipPattern { get; set; } = "^System|^Mscorlib|^msvcr120|^Netstandard|^Microsoft|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously Hosted|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|sni|sos|SOS.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|WindowsBase|ucrtbase|^DotNetCore.CAP|^MongoDB|^Confluent.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^NSwag|^Humanizer|^NJsonSchema|^Namotion|^ReSharper|^JetBrains|^NuGet|^ProxyGenerator|^testhost"; + public static string AssemblySkipPattern { get; set; } = "^System|^Mscorlib|^msvcr120|^Netstandard|^Microsoft|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously Hosted|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|sni|sos|SOS.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|WindowsBase|ucrtbase|^DotNetCore.CAP|^MongoDB|^Confluent.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^NSwag|^Humanizer|^NJsonSchema|^Namotion|^ReSharper|^JetBrains|^NuGet|^ProxyGenerator|^testhost|^MediatR|^Polly|^AspNetCore|^Minio|^SixLabors|^Quartz|^Hangfire|^Handlebars|^Serilog|^WebApiClientCore|^BouncyCastle|^RSAExtensions|^MartinCostello"; } \ No newline at end of file diff --git a/src/Util.Core/Infrastructure/ServiceContext.cs b/src/Util.Core/Infrastructure/ServiceContext.cs index ca4dc8e7d..5a922b4aa 100644 --- a/src/Util.Core/Infrastructure/ServiceContext.cs +++ b/src/Util.Core/Infrastructure/ServiceContext.cs @@ -1,6 +1,6 @@ using Util.Reflections; -namespace Util.Infrastructure; +namespace Util.Infrastructure; /// /// 服务上下文 @@ -10,10 +10,12 @@ public class ServiceContext { /// 初始化服务上下文 /// /// 主机生成器 + /// 程序集查找器 /// 类型查找器 - public ServiceContext( IHostBuilder hostBuilder, ITypeFinder typeFinder ) { - HostBuilder = hostBuilder; - TypeFinder = typeFinder; + public ServiceContext( IHostBuilder hostBuilder, IAssemblyFinder assemblyFinder, ITypeFinder typeFinder ) { + HostBuilder = hostBuilder ?? throw new ArgumentNullException( nameof( hostBuilder ) ); + AssemblyFinder = assemblyFinder ?? throw new ArgumentNullException( nameof( assemblyFinder ) ); + TypeFinder = typeFinder ?? throw new ArgumentNullException( nameof( typeFinder ) ); } /// @@ -21,6 +23,11 @@ public ServiceContext( IHostBuilder hostBuilder, ITypeFinder typeFinder ) { /// public IHostBuilder HostBuilder { get; } + /// + /// 程序集查找器 + /// + public IAssemblyFinder AssemblyFinder { get; } + /// /// 类型查找器 /// diff --git a/src/Util.Core/Properties/R.Designer.cs b/src/Util.Core/Properties/R.Designer.cs index 215fd508e..2effeff96 100644 --- a/src/Util.Core/Properties/R.Designer.cs +++ b/src/Util.Core/Properties/R.Designer.cs @@ -222,6 +222,15 @@ public static string SystemError { } } + /// + /// 查找类似 租户标识 的本地化字符串。 + /// + public static string TenantId { + get { + return ResourceManager.GetString("TenantId", resourceCulture); + } + } + /// /// 查找类似 类型 {0} 不是枚举 的本地化字符串。 /// diff --git a/src/Util.Core/Properties/R.resx b/src/Util.Core/Properties/R.resx index e584c26a8..89c75681a 100644 --- a/src/Util.Core/Properties/R.resx +++ b/src/Util.Core/Properties/R.resx @@ -172,6 +172,9 @@ 系统忙,请稍后再试 抛出系统异常时显示该消息 + + 租户标识 + 类型 {0} 不是枚举 diff --git a/src/Util.Core/Sessions/ISession.cs b/src/Util.Core/Sessions/ISession.cs index 75682ba9b..f4770acc8 100644 --- a/src/Util.Core/Sessions/ISession.cs +++ b/src/Util.Core/Sessions/ISession.cs @@ -1,9 +1,15 @@ -namespace Util.Sessions; +using Util.Dependency; + +namespace Util.Sessions; /// /// 用户会话 /// -public interface ISession { +public interface ISession : ISingletonDependency { + /// + /// 服务提供器 + /// + IServiceProvider ServiceProvider { get; } /// /// 是否认证 /// @@ -12,4 +18,8 @@ public interface ISession { /// 用户标识 /// string UserId { get; } + /// + /// 租户标识 + /// + string TenantId { get; } } \ No newline at end of file diff --git a/src/Util.Core/Sessions/NullSession.cs b/src/Util.Core/Sessions/NullSession.cs index 82508a063..e46ad3c12 100644 --- a/src/Util.Core/Sessions/NullSession.cs +++ b/src/Util.Core/Sessions/NullSession.cs @@ -1,21 +1,30 @@ -namespace Util.Sessions; +using Util.Dependency; + +namespace Util.Sessions; /// /// 空用户会话 /// +[Ioc(-9)] public class NullSession : ISession { /// - /// 是否认证 + /// 用户会话空实例 /// - public bool IsAuthenticated => false; + public static readonly ISession Instance = new NullSession(); /// - /// 用户标识 + /// 服务提供器 /// - public string UserId => string.Empty; + public IServiceProvider ServiceProvider => null; /// - /// 空用户会话实例 + /// 是否认证 /// - public static readonly ISession Instance = new NullSession(); + public bool IsAuthenticated => false; + + /// + public string UserId => string.Empty; + + /// + public string TenantId => string.Empty; } \ No newline at end of file diff --git a/src/Util.Data.Abstractions/ConnectionStringCollection.cs b/src/Util.Data.Abstractions/ConnectionStringCollection.cs new file mode 100644 index 000000000..acaf30fce --- /dev/null +++ b/src/Util.Data.Abstractions/ConnectionStringCollection.cs @@ -0,0 +1,27 @@ +namespace Util.Data; + +/// +/// 连接字符串集合 +/// +public class ConnectionStringCollection : Dictionary { + /// + /// 默认连接字符串名称,值: Default + /// + public const string DefaultName = "Default"; + + /// + /// 默认连接字符串 + /// + public string Default { + get => this.GetValue( DefaultName ); + set => this[DefaultName] = value; + } + + /// + /// 获取连接字符串 + /// + /// 连接字符串名称 + public string GetConnectionString( string name ) { + return ContainsKey( name ) ? this.GetValue( name ) : Default; + } +} \ No newline at end of file diff --git a/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs b/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs new file mode 100644 index 000000000..89408942b --- /dev/null +++ b/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs @@ -0,0 +1,38 @@ +namespace Util.Data; + +/// +/// 连接字符串名称 +/// +public class ConnectionStringNameAttribute : Attribute { + /// + /// 连接字符串名称 + /// + public string Name { get; } + + /// + /// 初始化连接字符串名称 + /// + /// 连接字符串名称 + public ConnectionStringNameAttribute( string name ) { + Name = name; + } + + /// + /// 获取连接字符串名称,如果未设置名称则返回类型名 + /// + /// 类型 + public static string GetName() { + return GetName( typeof( T ) ); + } + + /// + /// 获取连接字符串名称,如果未设置名称则返回类型名 + /// + /// 类型 + public static string GetName( Type type ) { + if ( type == null ) + return null; + var attribute = type.GetCustomAttribute(); + return attribute == null || attribute.Name.IsEmpty() ? type.Name : attribute.Name; + } +} \ No newline at end of file diff --git a/src/Util.Data.Abstractions/Usings.cs b/src/Util.Data.Abstractions/Usings.cs index 6eb207db5..18555658d 100644 --- a/src/Util.Data.Abstractions/Usings.cs +++ b/src/Util.Data.Abstractions/Usings.cs @@ -3,4 +3,5 @@ global using System.Collections.Generic; global using System.Linq; global using System.Threading; -global using System.Linq.Expressions; \ No newline at end of file +global using System.Linq.Expressions; +global using System.Reflection; \ No newline at end of file diff --git a/src/Util.Data.Core/Filters/IFilter.cs b/src/Util.Data.Core/Filters/IFilter.cs index 696dfd7fc..17d350193 100644 --- a/src/Util.Data.Core/Filters/IFilter.cs +++ b/src/Util.Data.Core/Filters/IFilter.cs @@ -27,7 +27,8 @@ public interface IFilter : ITransientDependency { /// 获取过滤表达式 /// /// 实体类型 - Expression> GetExpression() where TEntity : class; + /// 参数 + Expression> GetExpression( object state ) where TEntity : class; } /// diff --git a/src/Util.Data.Core/Filters/IFilterManager.cs b/src/Util.Data.Core/Filters/IFilterManager.cs index e74d414c6..c94eae5aa 100644 --- a/src/Util.Data.Core/Filters/IFilterManager.cs +++ b/src/Util.Data.Core/Filters/IFilterManager.cs @@ -26,4 +26,10 @@ public interface IFilterManager : IFilterSwitch,IScopeDependency { /// /// 过滤器类型 bool IsEnabled() where TFilterType : class; + /// + /// 获取过滤表达式 + /// + /// 实体类型 + /// 参数 + Expression> GetExpression( object state ) where TEntity : class; } \ No newline at end of file diff --git a/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj b/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj index 36476e463..faecf0214 100644 --- a/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj +++ b/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs index 09ed309bc..a5b5acd73 100644 --- a/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs @@ -12,4 +12,9 @@ public abstract class MySqlUnitOfWorkBase : UnitOfWorkBase { protected MySqlUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options ) : base( serviceProvider, options ) { } + + /// + protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) { + optionsBuilder.UseMySql( connectionString, ServerVersion.AutoDetect( connectionString ) ); + } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs index fa3f2033c..7b6902abf 100644 --- a/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs @@ -18,6 +18,11 @@ protected OracleUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptio : base( serviceProvider, options ) { } + /// + protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) { + optionsBuilder.UseOracle( connectionString ); + } + /// /// 配置扩展属性 /// diff --git a/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs index d4131c0bb..d202494e5 100644 --- a/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs @@ -12,4 +12,9 @@ public abstract class PgSqlUnitOfWorkBase : UnitOfWorkBase { protected PgSqlUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options ) : base( serviceProvider, options ) { } + + /// + protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) { + optionsBuilder.UseNpgsql( connectionString ); + } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs index 787091a51..e950c5b35 100644 --- a/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs @@ -1,6 +1,6 @@ using Util.Domain; -namespace Util.Data.EntityFrameworkCore; +namespace Util.Data.EntityFrameworkCore; /// /// SqlServer工作单元基类 @@ -15,11 +15,12 @@ protected SqlServerUnitOfWorkBase( IServiceProvider serviceProvider,DbContextOpt : base( serviceProvider, options ) { } - /// - /// 配置乐观锁 - /// - /// 模型生成器 - /// 实体类型 + /// + protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) { + optionsBuilder.UseSqlServer( connectionString ); + } + + /// protected override void ApplyVersion( ModelBuilder modelBuilder, IMutableEntityType entityType ) { if( typeof( IVersion ).IsAssignableFrom( entityType.ClrType ) == false ) return; diff --git a/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs index b908b3fda..700558552 100644 --- a/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs @@ -12,4 +12,9 @@ public abstract class SqliteUnitOfWorkBase : UnitOfWorkBase { protected SqliteUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options ) : base( serviceProvider, options ) { } + + /// + protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) { + optionsBuilder.UseSqlite( connectionString ); + } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj b/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj index eb6d842c3..15b803066 100644 --- a/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj +++ b/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs b/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs index 2150d969b..1ff8a3dc4 100644 --- a/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs +++ b/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs @@ -1,7 +1,4 @@ -using Util.Data.Filters; -using Util.Domain; - -namespace Util.Data.EntityFrameworkCore; +namespace Util.Data.EntityFrameworkCore; /// /// 过滤器操作扩展 @@ -22,4 +19,20 @@ public static void EnableDeleteFilter( this IFilterOperation source ) { public static IDisposable DisableDeleteFilter( this IFilterOperation source ) { return source.DisableFilter(); } + + /// + /// 启用租户过滤器 + /// + /// 源 + public static void EnableTenantFilter( this IFilterOperation source ) { + source.EnableFilter(); + } + + /// + /// 禁用租户过滤器 + /// + /// 源 + public static IDisposable DisableTenantFilter( this IFilterOperation source ) { + return source.DisableFilter(); + } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs b/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs index 0fe2df12a..fa7a600b5 100644 --- a/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs +++ b/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs @@ -1,6 +1,4 @@ -using Util.Domain; - -namespace Util.Data.EntityFrameworkCore.Filters; +namespace Util.Data.EntityFrameworkCore.Filters; /// /// 逻辑删除过滤器 @@ -10,7 +8,12 @@ public class DeleteFilter : FilterBase { /// 获取过滤表达式 /// /// 实体类型 - public override Expression> GetExpression() where TEntity : class { - return entity => !EF.Property( entity, "IsDeleted" ); + public override Expression> GetExpression( object state ) where TEntity : class { + var unitOfWork = state as UnitOfWorkBase; + Expression> expression = entity => !EF.Property( entity, "IsDeleted" ); + if ( unitOfWork == null ) + return expression; + Expression> isEnabled = entity => !unitOfWork.IsDeleteFilterEnabled; + return isEnabled.Or( expression ); } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs b/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs index 0875e7343..c24ce2509 100644 --- a/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs +++ b/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs @@ -1,6 +1,4 @@ -using Util.Data.Filters; - -namespace Util.Data.EntityFrameworkCore.Filters; +namespace Util.Data.EntityFrameworkCore.Filters; /// /// 数据过滤器基类 @@ -10,27 +8,27 @@ public abstract class FilterBase : IFilter where TFilt /// /// 过滤器是否启用 /// - public bool IsEnabled { get; private set; } = true; + public virtual bool IsEnabled { get; private set; } = true; /// /// 实体是否启用过滤器 /// /// 实体类型 - public bool IsEntityEnabled() { + public virtual bool IsEntityEnabled() { return typeof(TFilterType).IsAssignableFrom( typeof(TEntity) ); } /// /// 启用 /// - public void Enable() { + public virtual void Enable() { IsEnabled = true; } /// /// 禁用 /// - public IDisposable Disable() { + public virtual IDisposable Disable() { if ( IsEnabled == false ) return DisposeAction.Null; IsEnabled = false; @@ -41,5 +39,6 @@ public IDisposable Disable() { /// 获取过滤表达式 /// /// 实体类型 - public abstract Expression> GetExpression() where TEntity : class; + /// 参数 + public abstract Expression> GetExpression( object state ) where TEntity : class; } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs b/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs index 2a4401c27..05fcbf9de 100644 --- a/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs +++ b/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs @@ -1,6 +1,4 @@ -using Util.Data.Filters; - -namespace Util.Data.EntityFrameworkCore.Filters; +namespace Util.Data.EntityFrameworkCore.Filters; /// /// 数据过滤器管理器 @@ -57,35 +55,30 @@ public static void RemoveFilterType() { } /// - /// 启用过滤器 + /// 清空过滤器类型 /// - /// 过滤器类型 + public static void ClearFilterTypes() { + _filterTypes.Clear(); + } + + /// public void EnableFilter() where TFilterType : class { var filter = GetFilter(); filter?.Enable(); } - /// - /// 禁用过滤器 - /// - /// 过滤器类型 + /// public IDisposable DisableFilter() where TFilterType : class { var filter = GetFilter(); return filter?.Disable(); } - /// - /// 获取过滤器 - /// - /// 过滤器类型 + /// public IFilter GetFilter() where TFilterType : class { return GetFilter( typeof(TFilterType) ); } - /// - /// 获取过滤器 - /// - /// 过滤器类型 + /// public IFilter GetFilter( Type filterType ) { if( _filters.ContainsKey( filterType ) == false ) { var serviceType = typeof( IFilter<> ).MakeGenericType( filterType ); @@ -95,9 +88,7 @@ public IFilter GetFilter( Type filterType ) { return _filters[filterType]; } - /// - /// 实体是否启用过滤器 - /// + /// public bool IsEntityEnabled() { foreach( var type in _filterTypes ) { var filter = GetFilter( type ); @@ -107,14 +98,22 @@ public bool IsEntityEnabled() { return false; } - /// - /// 过滤器是否启用 - /// - /// 过滤器类型 + /// public bool IsEnabled() where TFilterType : class { var filter = GetFilter(); if ( filter == null ) return false; return filter.IsEnabled; } + + /// + public Expression> GetExpression( object state ) where TEntity : class { + Expression> expression = null; + foreach ( var type in _filterTypes ) { + var filter = GetFilter( type ); + if ( filter.IsEntityEnabled() ) + expression = expression.And( filter.GetExpression( state ) ); + } + return expression; + } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs b/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs new file mode 100644 index 000000000..7ce5ed569 --- /dev/null +++ b/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs @@ -0,0 +1,36 @@ +namespace Util.Data.EntityFrameworkCore.Filters; + +/// +/// 租户过滤器 +/// +public class TenantFilter : FilterBase { + /// + /// 租户管理器 + /// + private readonly ITenantManager _manager; + + /// + /// 初始化租户过滤器 + /// + /// 租户管理器 + public TenantFilter( ITenantManager manager ) { + _manager = manager ?? throw new ArgumentNullException( nameof( manager ) ); + } + + /// + /// 获取过滤表达式 + /// + /// 实体类型 + public override Expression> GetExpression( object state ) where TEntity : class { + if ( _manager.Enabled() == false ) + return null; + if ( _manager.IsDisableTenantFilter() ) + return null; + var unitOfWork = state as UnitOfWorkBase; + if ( unitOfWork == null ) + return null; + Expression> isEnabled = entity => !unitOfWork.IsTenantFilterEnabled; + Expression> expression = entity => EF.Property( entity, "TenantId" ) == unitOfWork.CurrentTenantId; + return isEnabled.Or( expression ); + } +} \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs b/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs index 9a63093eb..0bc989071 100644 --- a/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs +++ b/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs @@ -1,6 +1,4 @@ using Util.Data.EntityFrameworkCore.Filters; -using Util.Domain; -using Util.Infrastructure; namespace Util.Data.EntityFrameworkCore.Infrastructure; @@ -29,6 +27,7 @@ public class EntityFrameworkServiceRegistrar : IServiceRegistrar { /// 服务上下文 public Action Register( ServiceContext serviceContext ) { FilterManager.AddFilterType(); + FilterManager.AddFilterType(); return null; } } \ No newline at end of file diff --git a/src/Util.Data.EntityFrameworkCore/StoreBase.cs b/src/Util.Data.EntityFrameworkCore/StoreBase.cs index c76916a92..ff9cdfec9 100644 --- a/src/Util.Data.EntityFrameworkCore/StoreBase.cs +++ b/src/Util.Data.EntityFrameworkCore/StoreBase.cs @@ -1,7 +1,5 @@ -using Util.Data.Filters; -using Util.Data.Queries; +using Util.Data.Queries; using Util.Data.Stores; -using Util.Domain; using Util.Exceptions; namespace Util.Data.EntityFrameworkCore; diff --git a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs index d86f97033..8a8d29d40 100644 --- a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs @@ -1,8 +1,6 @@ using Util.Data.EntityFrameworkCore.ValueComparers; using Util.Data.EntityFrameworkCore.ValueConverters; -using Util.Data.Filters; using Util.Dates; -using Util.Domain; using Util.Domain.Auditing; using Util.Domain.Events; using Util.Domain.Extending; @@ -12,7 +10,7 @@ using Util.Properties; using Util.Sessions; -namespace Util.Data.EntityFrameworkCore; +namespace Util.Data.EntityFrameworkCore; /// /// 工作单元基类 @@ -31,10 +29,12 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt ServiceProvider = serviceProvider ?? throw new ArgumentNullException( nameof( serviceProvider ) ); Environment = serviceProvider.GetService(); FilterManager = ServiceProvider.GetService(); + TenantManager = ServiceProvider.GetService() ?? NullTenantManager.Instance; Session = serviceProvider.GetService() ?? NullSession.Instance; EventBus = serviceProvider.GetService() ?? NullEventBus.Instance; ActionManager = serviceProvider.GetService() ?? NullUnitOfWorkActionManager.Instance; - Events = new List(); + SaveBeforeEvents = new List(); + SaveAfterEvents = new List(); } #endregion @@ -58,9 +58,9 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt /// protected IFilterManager FilterManager { get; } /// - /// 逻辑删除过滤器是否启用 + /// 租户管理器 /// - protected virtual bool IsDeleteFilterEnabled => FilterManager?.IsEnabled() ?? false; + protected ITenantManager TenantManager { get; } /// /// 事件总线 /// @@ -70,9 +70,25 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt /// protected IUnitOfWorkActionManager ActionManager { get; } /// - /// 事件集合 + /// 保存前发送的事件集合 + /// + protected List SaveBeforeEvents { get; } + /// + /// 保存后发送的事件集合 + /// + protected List SaveAfterEvents { get; } + /// + /// 逻辑删除过滤器是否启用 /// - protected List Events { get; } + public virtual bool IsDeleteFilterEnabled => FilterManager?.IsEnabled() ?? false; + /// + /// 租户过滤器是否启用 + /// + public virtual bool IsTenantFilterEnabled => FilterManager?.IsEnabled() ?? false; + /// + /// 当前租户标识 + /// + public virtual string CurrentTenantId => TenantManager.GetTenantId(); /// /// 是否清除字符串两端的空白,默认为true /// @@ -125,6 +141,7 @@ public IDisposable DisableFilter() where TFilterType : class { /// 配置生成器 protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder ) { ConfigLog( optionsBuilder ); + ConfigTenant( optionsBuilder ); } #endregion @@ -145,6 +162,37 @@ protected virtual void ConfigLog( DbContextOptionsBuilder optionsBuilder ) { #endregion + #region ConfigTenant(配置租户) + + /// + /// 配置租户 + /// + /// 配置生成器 + protected virtual void ConfigTenant( DbContextOptionsBuilder optionsBuilder ) { + if ( TenantManager.Enabled() == false ) + return; + if ( TenantManager.AllowMultipleDatabase() == false ) + return; + var tenant = TenantManager.GetTenant(); + if ( tenant == null ) + return; + var name = ConnectionStringNameAttribute.GetName( GetType() ); + var connectionString = tenant.ConnectionStrings.GetConnectionString( name ); + if ( connectionString.IsEmpty() ) + return; + ConfigTenantConnectionString( optionsBuilder, connectionString ); + } + + /// + /// 配置租户连接字符串 + /// + /// 配置生成器 + /// 连接字符串 + protected virtual void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder,string connectionString ) { + } + + #endregion + #region OnModelCreating(配置模型) /// @@ -158,6 +206,7 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) { ApplyExtraProperties( modelBuilder, entityType ); ApplyVersion( modelBuilder, entityType ); ApplyIsDeleted( modelBuilder, entityType ); + ApplyTenantId( modelBuilder, entityType ); ApplyUtc( modelBuilder, entityType ); ApplyTrimString( modelBuilder, entityType ); } @@ -198,7 +247,10 @@ protected virtual void ApplyFiltersImp( ModelBuilder modelBuilder ) whe return; if ( FilterManager.IsEntityEnabled() == false ) return; - modelBuilder.Entity().HasQueryFilter( GetFilterExpression() ); + var expression = GetFilterExpression(); + if ( expression == null ) + return; + modelBuilder.Entity().HasQueryFilter( expression ); } /// @@ -206,20 +258,7 @@ protected virtual void ApplyFiltersImp( ModelBuilder modelBuilder ) whe /// /// 实体类型 protected virtual Expression> GetFilterExpression() where TEntity : class { - return GetDeleteFilterExpression(); - } - - /// - /// 获取逻辑删除过滤器表达式 - /// - /// 实体类型 - protected virtual Expression> GetDeleteFilterExpression() where TEntity : class { - var filter = FilterManager.GetFilter(); - if ( filter.IsEntityEnabled() == false ) - return null; - var expression = filter.GetExpression(); - Expression> result = entity => !IsDeleteFilterEnabled; - return result.Or( expression ); + return FilterManager.GetExpression( this ); } #endregion @@ -281,6 +320,24 @@ protected virtual void ApplyIsDeleted( ModelBuilder modelBuilder, IMutableEntity #endregion + #region ApplyTenantId(配置租户标识) + + /// + /// 配置租户标识 + /// + /// 模型生成器 + /// 实体类型 + protected virtual void ApplyTenantId( ModelBuilder modelBuilder, IMutableEntityType entityType ) { + if ( typeof( ITenant ).IsAssignableFrom( entityType.ClrType ) == false ) + return; + modelBuilder.Entity( entityType.ClrType ) + .Property( "TenantId" ) + .HasColumnName( "TenantId" ) + .HasComment( R.TenantId ); + } + + #endregion + #region ApplyUtc(配置Utc日期) /// @@ -349,7 +406,7 @@ public async Task CommitAsync() { /// 保存 /// public override async Task SaveChangesAsync( CancellationToken cancellationToken = default ) { - SaveChangesBefore(); + await SaveChangesBefore(); var result = await base.SaveChangesAsync( cancellationToken ); await SaveChangesAfter(); return result; @@ -362,8 +419,10 @@ public async Task CommitAsync() { /// /// 保存前操作 /// - protected virtual void SaveChangesBefore() { + protected virtual async Task SaveChangesBefore() { foreach ( var entry in ChangeTracker.Entries() ) { + UpdateTenantId( entry ); + AddDomainEvents( entry ); switch ( entry.State ) { case EntityState.Added: AddBefore( entry ); @@ -376,6 +435,47 @@ protected virtual void SaveChangesBefore() { break; } } + await PublishSaveBeforeEventsAsync(); + } + + #endregion + + #region UpdateTenantId(更新租户标识) + + /// + /// 更新租户标识 + /// + protected virtual void UpdateTenantId( EntityEntry entry ) { + if ( TenantManager.Enabled() == false ) + return; + if ( entry.Entity is not ITenant tenant ) + return; + var tenantId = TenantManager.GetTenantId(); + if ( tenantId.IsEmpty() ) + return; + tenant.TenantId = tenantId; + } + + #endregion + + #region AddDomainEvents(添加领域事件) + + /// + /// 添加领域事件 + /// + protected virtual void AddDomainEvents( EntityEntry entry ) { + if ( entry.Entity is not IDomainEventManager eventManager ) + return; + if ( eventManager.DomainEvents == null ) + return; + foreach ( var domainEvent in eventManager.DomainEvents ) { + if ( domainEvent is IIntegrationEvent ) { + SaveAfterEvents.Add( domainEvent ); + continue; + } + SaveBeforeEvents.Add( domainEvent ); + } + eventManager.ClearDomainEvents(); } #endregion @@ -476,7 +576,7 @@ protected virtual byte[] GetVersion() { protected virtual void AddEntityChangedEvent( object entity, EntityChangeType changeType ) { var eventType = typeof( EntityChangedEvent<> ).MakeGenericType( entity.GetType() ); var @event = Reflection.CreateInstance( eventType, entity, changeType ); - Events.Add( @event ); + SaveAfterEvents.Add( @event ); } #endregion @@ -488,7 +588,7 @@ protected virtual void AddEntityChangedEvent( object entity, EntityChangeType ch /// protected virtual void AddEntityCreatedEvent( object entity ) { var @event = CreateEntityEvent( typeof( EntityCreatedEvent<> ), entity ); - Events.Add( @event ); + SaveAfterEvents.Add( @event ); AddEntityChangedEvent( entity, EntityChangeType.Created ); } @@ -513,7 +613,7 @@ protected virtual void AddEntityUpdatedEvent( object entity ) { return; } var @event = CreateEntityEvent( typeof( EntityUpdatedEvent<> ), entity ); - Events.Add( @event ); + SaveAfterEvents.Add( @event ); AddEntityChangedEvent( entity, EntityChangeType.Updated ); } @@ -526,44 +626,59 @@ protected virtual void AddEntityUpdatedEvent( object entity ) { /// protected virtual void AddEntityDeletedEvent( object entity ) { var @event = CreateEntityEvent( typeof( EntityDeletedEvent<> ), entity ); - Events.Add( @event ); + SaveAfterEvents.Add( @event ); AddEntityChangedEvent( entity, EntityChangeType.Deleted ); } #endregion + #region PublishSaveBeforeEventsAsync(发布保存前事件) + + /// + /// 发布保存前事件 + /// + protected virtual async Task PublishSaveBeforeEventsAsync() { + if ( SaveBeforeEvents.Count == 0 ) + return; + var events = new List( SaveBeforeEvents ); + SaveBeforeEvents.Clear(); + await EventBus.PublishAsync( events ); + } + + #endregion + #region SaveChangesAfter(保存后操作) /// /// 保存后操作 /// protected virtual async Task SaveChangesAfter() { + await PublishSaveAfterEventsAsync(); await ExecuteActionsAsync(); - await PublishEventsAsync(); } #endregion - #region ExecuteActionsAsync(执行工作单元操作集合) + #region PublishSaveAfterEventsAsync(发布保存后事件) /// - /// 执行工作单元操作集合 + /// 发布保存后事件 /// - protected virtual async Task ExecuteActionsAsync() { - await ActionManager.ExecuteAsync(); + protected virtual async Task PublishSaveAfterEventsAsync() { + var events = new List( SaveAfterEvents ); + SaveAfterEvents.Clear(); + await EventBus.PublishAsync( events ); } #endregion - #region PublishEventsAsync(发布事件) + #region ExecuteActionsAsync(执行工作单元操作集合) /// - /// 发布事件 + /// 执行工作单元操作集合 /// - protected virtual async Task PublishEventsAsync() { - var events = new List( Events ); - Events.Clear(); - await EventBus.PublishAsync( events ); + protected virtual async Task ExecuteActionsAsync() { + await ActionManager.ExecuteAsync(); } #endregion diff --git a/src/Util.Data.EntityFrameworkCore/Usings.cs b/src/Util.Data.EntityFrameworkCore/Usings.cs index bdf0a4378..8558fc694 100644 --- a/src/Util.Data.EntityFrameworkCore/Usings.cs +++ b/src/Util.Data.EntityFrameworkCore/Usings.cs @@ -19,3 +19,7 @@ global using Microsoft.EntityFrameworkCore.ChangeTracking; global using Microsoft.EntityFrameworkCore.Storage.ValueConversion; global using Microsoft.EntityFrameworkCore.Metadata; +global using Util.Tenants; +global using Util.Domain; +global using Util.Infrastructure; +global using Util.Data.Filters; \ No newline at end of file diff --git a/src/Util.Domain/Entities/AggregateRoot.cs b/src/Util.Domain/Entities/AggregateRoot.cs index 6b234059f..63758ff36 100644 --- a/src/Util.Domain/Entities/AggregateRoot.cs +++ b/src/Util.Domain/Entities/AggregateRoot.cs @@ -1,4 +1,6 @@ -namespace Util.Domain.Entities; +using Util.Domain.Events; + +namespace Util.Domain.Entities; /// /// 聚合根 @@ -18,7 +20,12 @@ protected AggregateRoot( Guid id ) : base( id ) { /// /// 实体类型 /// 标识类型 -public abstract class AggregateRoot : EntityBase, IAggregateRoot where TEntity : IAggregateRoot { +public abstract class AggregateRoot : EntityBase, IAggregateRoot, IDomainEventManager where TEntity : IAggregateRoot { + /// + /// 领域事件列表 + /// + private List _domainEvents; + /// /// 初始化聚合根 /// @@ -30,4 +37,28 @@ protected AggregateRoot( TKey id ) : base( id ) { /// 版本号 /// public byte[] Version { get; set; } + + /// + [NotMapped] + public IReadOnlyCollection DomainEvents => _domainEvents?.AsReadOnly(); + + /// + public void AddDomainEvent( IEvent @event ) { + if ( @event == null ) + return; + _domainEvents ??= new List(); + _domainEvents.Add( @event ); + } + + /// + public void RemoveDomainEvent( IEvent @event ) { + if ( @event == null ) + return; + _domainEvents?.Remove( @event ); + } + + /// + public void ClearDomainEvents() { + _domainEvents?.Clear(); + } } \ No newline at end of file diff --git a/src/Util.Domain/Events/IDomainEventManager.cs b/src/Util.Domain/Events/IDomainEventManager.cs new file mode 100644 index 000000000..ad34f746e --- /dev/null +++ b/src/Util.Domain/Events/IDomainEventManager.cs @@ -0,0 +1,25 @@ +namespace Util.Domain.Events; + +/// +/// 领域事件服务 +/// +public interface IDomainEventManager { + /// + /// 领域事件集合 + /// + IReadOnlyCollection DomainEvents { get; } + /// + /// 添加领域事件 + /// + /// 领域事件 + void AddDomainEvent( IEvent @event ); + /// + /// 移除领域事件 + /// + /// 领域事件 + void RemoveDomainEvent( IEvent @event ); + /// + /// 清空领域事件 + /// + void ClearDomainEvents(); +} \ No newline at end of file diff --git a/src/Util.Domain/Usings.cs b/src/Util.Domain/Usings.cs index 39f0dc68b..cca7e5a3f 100644 --- a/src/Util.Domain/Usings.cs +++ b/src/Util.Domain/Usings.cs @@ -6,4 +6,6 @@ global using System.Text; global using System.Reflection; global using System.Linq.Expressions; -global using System.ComponentModel.DataAnnotations; \ No newline at end of file +global using System.ComponentModel.DataAnnotations; +global using System.ComponentModel.DataAnnotations.Schema; +global using Util.Events; \ No newline at end of file diff --git a/src/Util.Events.Abstractions/IIntegrationEventBus.cs b/src/Util.Events.Abstractions/IIntegrationEventBus.cs index 5b73d1825..ef72eb6af 100644 --- a/src/Util.Events.Abstractions/IIntegrationEventBus.cs +++ b/src/Util.Events.Abstractions/IIntegrationEventBus.cs @@ -4,11 +4,6 @@ /// 基于消息的集成事件总线 /// public interface IIntegrationEventBus : ITransientDependency { - /// - /// 设置是否立即发送事件 - /// - /// 是否立即发送事件,如果希望发送操作延迟到工作单元提交成功后,则设置为false - IIntegrationEventBus SendNow( bool isSend ); /// /// 设置发布订阅配置名称 /// diff --git a/src/Util.Events.Abstractions/IIntegrationEventExtend.cs b/src/Util.Events.Abstractions/IIntegrationEventExtend.cs deleted file mode 100644 index 2ddf97110..000000000 --- a/src/Util.Events.Abstractions/IIntegrationEventExtend.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Util.Events; - -/// -/// 集成事件扩展属性 -/// -public interface IIntegrationEventExtend { - /// - /// 是否立即发送,默认值: true - /// - bool? SendNow { get; } - /// - /// 发布订阅名称,默认值: pubsub - /// - string PubsubName { get; } - /// - /// 事件主题,默认为事件类型名称 - /// - string Topic { get; } -} \ No newline at end of file diff --git a/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj b/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj new file mode 100644 index 000000000..fe2a2d455 --- /dev/null +++ b/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj @@ -0,0 +1,37 @@ + + + + $(NetTargetFramework) + icon.jpg + Util.Events.MediatR + Util.Events + Util.Events.MediatR是Util应用框架基于MediatR的事件总线操作类库 + + + + + .\obj\Debug\$(NetTargetFramework)\Util.Events.MediatR.xml + + + + + .\obj\Release\$(NetTargetFramework)\Util.Events.MediatR.xml + + + + + True + False + + + + + + + + + + + + + diff --git a/src/Util.Events.MediatR/AppBuilderExtensions.cs b/src/Util.Events.MediatR/AppBuilderExtensions.cs new file mode 100644 index 000000000..738097ba6 --- /dev/null +++ b/src/Util.Events.MediatR/AppBuilderExtensions.cs @@ -0,0 +1,35 @@ +using Util.Configs; + +namespace Util.Events; + +/// +/// MediatR事件总线操作扩展 +/// +public static class AppBuilderExtensions { + /// + /// 配置MediatR事件总线操作 + /// + /// 应用生成器 + public static IAppBuilder AddMediatR( this IAppBuilder builder ) { + builder.CheckNull( nameof( builder ) ); + MediatROptions.IsScan = true; + builder.Host.ConfigureServices( ( context, services ) => { + services.TryAddTransient(); + } ); + return builder; + } + + /// + /// 配置MediatR事件总线操作 + /// + /// 应用生成器 + /// 事件总线配置操作 + public static IAppBuilder AddMediatR( this IAppBuilder builder, Action setupAction ) { + builder.CheckNull( nameof( builder ) ); + builder.Host.ConfigureServices( ( context, services ) => { + services.TryAddTransient(); + services.AddMediatR( setupAction ); + } ); + return builder; + } +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/EventBase.cs b/src/Util.Events.MediatR/EventBase.cs new file mode 100644 index 000000000..ddf40d589 --- /dev/null +++ b/src/Util.Events.MediatR/EventBase.cs @@ -0,0 +1,7 @@ +namespace Util.Events; + +/// +/// 事件 +/// +public abstract class EventBase : IEvent, INotification { +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/EventHandlerBase.cs b/src/Util.Events.MediatR/EventHandlerBase.cs new file mode 100644 index 000000000..e79c44525 --- /dev/null +++ b/src/Util.Events.MediatR/EventHandlerBase.cs @@ -0,0 +1,30 @@ +namespace Util.Events; + +/// +/// 基于MediatR的本地事件处理器基类 +/// +/// 事件类型 +public abstract class EventHandlerBase : INotificationHandler where TEvent : IEvent, INotification { + /// + /// 是否启用 + /// + public virtual bool Enabled => true; + + /// + /// 处理事件 + /// + /// 事件 + /// 取消令牌 + public async Task Handle( TEvent @event, CancellationToken cancellationToken ) { + if ( Enabled == false ) + return; + await HandleAsync( @event, cancellationToken ); + } + + /// + /// 处理事件 + /// + /// 事件 + /// 取消令牌 + public abstract Task HandleAsync( TEvent @event, CancellationToken cancellationToken ); +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs b/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs new file mode 100644 index 000000000..0ff355c17 --- /dev/null +++ b/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs @@ -0,0 +1,41 @@ +namespace Util.Events.Infrastructure; + +/// +/// MediatR本地事件总线服务注册器 +/// +public class MediatREventBusServiceRegistrar : IServiceRegistrar { + /// + /// 获取服务名 + /// + public static string ServiceName => "Util.Events.MediatR.Infrastructure.LocalEventBusServiceRegistrar"; + + /// + /// 排序号 + /// + public int OrderId => 511; + + /// + /// 是否启用 + /// + public bool Enabled => ServiceRegistrarConfig.IsEnabled( ServiceName ); + + /// + /// 注册服务 + /// + /// 服务上下文 + public Action Register( ServiceContext serviceContext ) { + serviceContext.HostBuilder.ConfigureServices( ( context, services ) => { + RegisterMediatR( services, serviceContext.AssemblyFinder ); + } ); + return null; + } + + /// + /// 注册MediatR + /// + private void RegisterMediatR( IServiceCollection services, IAssemblyFinder finder ) { + if ( MediatROptions.IsScan == false ) + return; + services.AddMediatR( t => t.RegisterServicesFromAssemblies( finder.Find().ToArray() ) ); + } +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs b/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs new file mode 100644 index 000000000..2b1c713a1 --- /dev/null +++ b/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs @@ -0,0 +1,24 @@ +namespace Util.Events.Infrastructure; + +/// +/// MediatR本地事件总线服务注册器配置扩展 +/// +public static class ServiceRegistrarConfigExtensions { + /// + /// 启用MediatR本地事件总线服务注册器 + /// + /// 服务注册器配置 + public static ServiceRegistrarConfig EnableMediatREventBusServiceRegistrar( this ServiceRegistrarConfig config ) { + ServiceRegistrarConfig.Enable( MediatREventBusServiceRegistrar.ServiceName ); + return config; + } + + /// + ///禁用MediatR本地事件总线服务注册器 + /// + /// 服务注册器配置 + public static ServiceRegistrarConfig DisableMediatREventBusServiceRegistrar( this ServiceRegistrarConfig config ) { + ServiceRegistrarConfig.Disable( MediatREventBusServiceRegistrar.ServiceName ); + return config; + } +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/MediatREventBus.cs b/src/Util.Events.MediatR/MediatREventBus.cs new file mode 100644 index 000000000..02b7ec6cb --- /dev/null +++ b/src/Util.Events.MediatR/MediatREventBus.cs @@ -0,0 +1,27 @@ +namespace Util.Events; + +/// +/// 基于MediatR的本地事件总线 +/// +public class MediatREventBus : ILocalEventBus { + /// + /// 事件发布器 + /// + private readonly IMediator _publisher; + + /// + /// 初始化基于MediatR的本地事件总线 + /// + /// 事件发布器 + public MediatREventBus( IMediator publisher ) { + _publisher = publisher ?? throw new ArgumentNullException( nameof( publisher ) ); + } + + /// + public async Task PublishAsync( TEvent @event,CancellationToken cancellationToken = default ) where TEvent : IEvent { + cancellationToken.ThrowIfCancellationRequested(); + if ( @event == null ) + return; + await _publisher.Publish( @event, cancellationToken ); + } +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/MediatROptions.cs b/src/Util.Events.MediatR/MediatROptions.cs new file mode 100644 index 000000000..b6042ceab --- /dev/null +++ b/src/Util.Events.MediatR/MediatROptions.cs @@ -0,0 +1,11 @@ +namespace Util.Events; + +/// +/// MediatR事件总线配置 +/// +public static class MediatROptions { + /// + /// 是否扫描加载程序集 + /// + public static bool IsScan { get; set; } +} \ No newline at end of file diff --git a/src/Util.Events.MediatR/Usings.cs b/src/Util.Events.MediatR/Usings.cs new file mode 100644 index 000000000..9e38359f6 --- /dev/null +++ b/src/Util.Events.MediatR/Usings.cs @@ -0,0 +1,8 @@ +global using System; +global using System.Threading.Tasks; +global using System.Threading; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.DependencyInjection; +global using MediatR; +global using Util.Infrastructure; +global using Util.Reflections; \ No newline at end of file diff --git a/src/Util.Events.Abstractions/ILocalEventHandler.cs b/src/Util.Events/ILocalEventHandler.cs similarity index 100% rename from src/Util.Events.Abstractions/ILocalEventHandler.cs rename to src/Util.Events/ILocalEventHandler.cs diff --git a/src/Util.Events/LocalEventBus.cs b/src/Util.Events/LocalEventBus.cs index 6e0af22ac..71ce98860 100644 --- a/src/Util.Events/LocalEventBus.cs +++ b/src/Util.Events/LocalEventBus.cs @@ -1,6 +1,4 @@ -using Util.Data; - -namespace Util.Events; +namespace Util.Events; /// /// 基于内存的本地事件总线 @@ -10,10 +8,6 @@ public class LocalEventBus : ILocalEventBus { /// 服务提供器 /// private readonly IServiceProvider _serviceProvider; - /// - /// 工作单元操作管理器 - /// - private readonly IUnitOfWorkActionManager _actionManager; /// /// 初始化本地事件总线 @@ -21,7 +15,6 @@ public class LocalEventBus : ILocalEventBus { /// 服务提供器 public LocalEventBus( IServiceProvider serviceProvider ) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException( nameof( serviceProvider ) ); - _actionManager = _serviceProvider.GetService(); } /// @@ -29,21 +22,7 @@ public LocalEventBus( IServiceProvider serviceProvider ) { cancellationToken.ThrowIfCancellationRequested(); if ( @event == null ) return; - if ( @event is not IIntegrationEvent integrationEvent ) { - await PublishLocalEventAsync( @event, cancellationToken ); - return; - } - if( _actionManager == null ) { - await PublishLocalEventAsync( @event, cancellationToken ); - return; - } - if ( integrationEvent is IIntegrationEventExtend { SendNow: true } ) { - await PublishLocalEventAsync( @event, cancellationToken ); - return; - } - _actionManager.Register( async () => { - await PublishLocalEventAsync( @event, cancellationToken ); - } ); + await PublishLocalEventAsync( @event, cancellationToken ); } /// diff --git a/src/Util.Generators/Configuration/ProjectOptions.cs b/src/Util.Generators/Configuration/ProjectOptions.cs index d741a62bd..cc1d93b59 100644 --- a/src/Util.Generators/Configuration/ProjectOptions.cs +++ b/src/Util.Generators/Configuration/ProjectOptions.cs @@ -50,6 +50,10 @@ public ProjectOptions() { /// public string ApiPort { get; set; } /// + /// 是否启用架构 + /// + public bool EnableSchema { get; set; } + /// /// 项目类型 /// public ProjectType? ProjectType { get; set; } @@ -60,5 +64,5 @@ public ProjectOptions() { /// /// 扩展 /// - public object Extend { get; set; } + public string Extend { get; set; } } \ No newline at end of file diff --git a/src/Util.Generators/Contexts/GeneratorContextBuilder.cs b/src/Util.Generators/Contexts/GeneratorContextBuilder.cs index 36a21f284..945bccc1e 100644 --- a/src/Util.Generators/Contexts/GeneratorContextBuilder.cs +++ b/src/Util.Generators/Contexts/GeneratorContextBuilder.cs @@ -111,6 +111,7 @@ protected async Task CreateProjectContext( GeneratorContext gene I18n = projectOptions.I18n, ProjectType = projectOptions.ProjectType, ApiPort = projectOptions.ApiPort, + EnableSchema = projectOptions.EnableSchema, Extend = projectOptions.Extend }; if ( projectOptions.Enabled == false ) diff --git a/src/Util.Generators/Contexts/ProjectContext.cs b/src/Util.Generators/Contexts/ProjectContext.cs index d1bf1194c..2938fc014 100644 --- a/src/Util.Generators/Contexts/ProjectContext.cs +++ b/src/Util.Generators/Contexts/ProjectContext.cs @@ -76,6 +76,11 @@ public ProjectContext( GeneratorContext generatorContext ) { /// public ProjectType? ProjectType { get; set; } + /// + /// 是否启用架构 + /// + public bool EnableSchema { get; set; } + /// /// 客户端配置 /// @@ -84,7 +89,7 @@ public ProjectContext( GeneratorContext generatorContext ) { /// /// 扩展 /// - public object Extend { get; set; } + public string Extend { get; set; } /// /// 生成器上下文 @@ -105,7 +110,7 @@ public ProjectContext( GeneratorContext generatorContext ) { /// 获取扩展 /// public T GetExtend() { - return Util.Helpers.Convert.To( Extend ); + return Util.Helpers.Json.ToObject( Extend ); } /// @@ -136,18 +141,11 @@ public ProjectContext Clone( GeneratorContext generatorContext ) { I18n = I18n, ProjectType = ProjectType, ApiPort = ApiPort, - Extend = CloneExtend() + EnableSchema = EnableSchema, + Extend = Extend }; result.Schemas.AddRange( Schemas ); Entities.ForEach( entity => result.Entities.Add( entity.Clone( result ) ) ); return result; } - - /// - /// 复制扩展 - /// - private object CloneExtend() { - var json = Util.Helpers.Json.ToJson( Extend ); - return Util.Helpers.Json.ToObject( json ); - } } \ No newline at end of file diff --git a/src/Util.Images.ImageSharp/01-Util.Images.ImageSharp.csproj b/src/Util.Images.ImageSharp/01-Util.Images.ImageSharp.csproj index badd4a2b0..21c30141b 100644 --- a/src/Util.Images.ImageSharp/01-Util.Images.ImageSharp.csproj +++ b/src/Util.Images.ImageSharp/01-Util.Images.ImageSharp.csproj @@ -31,7 +31,7 @@ - + diff --git a/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj b/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj index 141913618..86d597162 100644 --- a/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj +++ b/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/Util.Microservices.Dapr/AppBuilderExtensions.cs b/src/Util.Microservices.Dapr/AppBuilderExtensions.cs index 72053f9b3..0f713dc19 100644 --- a/src/Util.Microservices.Dapr/AppBuilderExtensions.cs +++ b/src/Util.Microservices.Dapr/AppBuilderExtensions.cs @@ -32,9 +32,9 @@ public static IAppBuilder AddDapr( this IAppBuilder builder, Action /// Dapr配置操作 /// Dapr客户端生成操作 public static IAppBuilder AddDapr( this IAppBuilder builder, Action setupAction, Action buildAction ) { + builder.CheckNull( nameof( builder ) ); var options = new DaprOptions(); setupAction?.Invoke( options ); - builder.CheckNull( nameof( builder ) ); builder.Host.ConfigureServices( ( context, services ) => { services.AddDaprClient( clientBuilder => { clientBuilder.UseJsonSerializationOptions( GetJsonSerializerOptions() ); diff --git a/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs b/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs index d5523aa02..db2575802 100644 --- a/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs +++ b/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs @@ -22,7 +22,7 @@ public DaprMicroserviceClient( DaprClient client, HttpClient httpClient, IOption ServiceInvocation = new DaprServiceInvocation( client, options, loggerFactory ).Service( appId ); IntegrationEventBus = new DaprIntegrationEventBus( client, options, loggerFactory, serviceProvider ); var keyGenerator = serviceProvider.GetService(); - StateManage = new DaprStateManage( client, options, loggerFactory, keyGenerator ); + StateManager = new DaprStateManager( client, options, loggerFactory, keyGenerator ); } /// @@ -43,5 +43,5 @@ public DaprMicroserviceClient( DaprClient client, HttpClient httpClient, IOption /// /// 状态管理 /// - public IStateManage StateManage { get; } + public IStateManager StateManager { get; } } \ No newline at end of file diff --git a/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs b/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs index d7146785d..f7e346152 100644 --- a/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs +++ b/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs @@ -22,10 +22,6 @@ public class DaprIntegrationEventBus : IIntegrationEventBus { /// protected readonly ILogger Logger; /// - /// 工作单元操作管理器 - /// - protected readonly IUnitOfWorkActionManager ActionManager; - /// /// 发布订阅回调操作 /// protected IPubsubCallback PubsubCallback; @@ -34,10 +30,6 @@ public class DaprIntegrationEventBus : IIntegrationEventBus { /// protected IIntegrationEventManager EventManager; /// - /// 是否立即发送事件 - /// - protected bool? IsSend; - /// /// 发布订阅配置名称 /// protected string Pubsub; @@ -90,8 +82,6 @@ public DaprIntegrationEventBus( DaprClient client, IOptions options Options = options?.Value ?? new DaprOptions(); Logger = loggerFactory?.CreateLogger( typeof( DaprEventBus ) ) ?? NullLogger.Instance; serviceProvider.CheckNull( nameof( serviceProvider ) ); - ActionManager = serviceProvider.GetService(); - ActionManager.CheckNull( nameof( ActionManager ) ); PubsubCallback = serviceProvider.GetService() ?? NullPubsubCallback.Instance; EventManager = serviceProvider.GetRequiredService(); Headers = new Dictionary(); @@ -102,16 +92,6 @@ public DaprIntegrationEventBus( DaprClient client, IOptions options #endregion - #region SendNow - - /// - public IIntegrationEventBus SendNow( bool isSend ) { - IsSend = isSend; - return this; - } - - #endregion - #region PubsubName /// @@ -276,13 +256,7 @@ public IIntegrationEventBus OnAfter( Func { - await PublishEventAsync( @event, cancellationToken ); - } ); + await PublishEventAsync( @event, cancellationToken ); } /// @@ -335,35 +309,17 @@ private IDictionary GetImportHeaders() { /// 初始化 /// protected void Init( IIntegrationEvent integrationEvent ) { - InitIsSend( integrationEvent ); InitPubsubName( integrationEvent ); InitTopic( integrationEvent ); } /// - /// 初始化是否立即发送 - /// - protected void InitIsSend( IIntegrationEvent integrationEvent ) { - if ( IsSend != null ) - return; - IsSend = true; - if ( integrationEvent is not IIntegrationEventExtend extend ) - return; - if ( extend.SendNow != null ) - IsSend = extend.SendNow; - } - - /// - /// 初始化发布订阅配置名称 + /// 初始化发布订阅名称 /// protected void InitPubsubName( IIntegrationEvent integrationEvent ) { if ( Pubsub.IsEmpty() == false ) return; - Pubsub = "pubsub"; - if ( integrationEvent is not IIntegrationEventExtend extend ) - return; - if ( extend.PubsubName.IsEmpty() == false ) - Pubsub = extend.PubsubName; + Pubsub = PubsubNameAttribute.GetName( integrationEvent.GetType() ); } /// @@ -372,11 +328,7 @@ protected void InitPubsubName( IIntegrationEvent integrationEvent ) { protected void InitTopic( IIntegrationEvent integrationEvent ) { if ( TopicName.IsEmpty() == false ) return; - TopicName = integrationEvent.GetType().Name; - if ( integrationEvent is not IIntegrationEventExtend extend ) - return; - if ( extend.Topic.IsEmpty() == false ) - TopicName = extend.Topic; + TopicName = TopicNameAttribute.GetName( integrationEvent.GetType() ); } /// diff --git a/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs b/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs index 76f5c2e8a..c1546eba6 100644 --- a/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs +++ b/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs @@ -24,7 +24,10 @@ public EventLogPubsubCallback( IIntegrationEventManager manager ) { /// public virtual async Task OnPublishAfter( PubsubArgument argument, CancellationToken cancellationToken = default ) { - await Manager.CreatePublishLogAsync( argument, cancellationToken ); + var result = await Manager.CreatePublishLogAsync( argument, cancellationToken ); + if ( result == NullIntegrationEventLog.Instance ) + return; + await Manager.IncrementAsync( cancellationToken ); } /// diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs new file mode 100644 index 000000000..5626b2878 --- /dev/null +++ b/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs @@ -0,0 +1,17 @@ +namespace Util.Microservices.Dapr.Events; + +/// +/// 集成事件计数 +/// +public class IntegrationEventCount : IDataKey, IETag { + /// + public string Id { get; set; } + + /// + public string ETag { get; set; } + + /// + /// 集成事件总条数 + /// + public int? Count { get; set; } +} \ No newline at end of file diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs index e43a341b9..3a6e33890 100644 --- a/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs +++ b/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs @@ -7,29 +7,54 @@ public class IntegrationEventLogStore : IIntegrationEventLogStore { /// /// 初始化集成事件日志记录存储器 /// - /// 状态管理操作 + /// 状态管理操作 /// 配置 - public IntegrationEventLogStore( IStateManage stateManage, IOptions options ) { - StateManage = stateManage ?? throw new ArgumentNullException( nameof( stateManage ) ); - StateManage.StoreName( options?.Value.Pubsub?.EventLogStoreName ?? "statestore" ); + public IntegrationEventLogStore( IStateManager stateManager, IOptions options ) { + StateManager = stateManager ?? throw new ArgumentNullException( nameof( stateManager ) ); + StateManager.StoreName( options?.Value.Pubsub?.EventLogStoreName ?? "statestore" ); } + /// + /// 集成事件总条数键名 + /// + public const string KeyCount = "IntegrationEventCount"; /// /// 状态管理操作 /// - protected IStateManage StateManage; + protected IStateManager StateManager; /// public virtual async Task GetAsync( string eventId, CancellationToken cancellationToken = default ) { - return await StateManage.GetByIdAsync( eventId, cancellationToken ); + return await StateManager.GetByIdAsync( eventId, cancellationToken ); + } + + /// + public virtual async Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) { + await StateManager.SaveAsync( eventLog, cancellationToken ); + } + + /// + public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) { + var result = await GetIntegrationEventCount( cancellationToken ); + result.Count = result.Count.SafeValue() + 1; + await StateManager.SaveAsync( result, cancellationToken, KeyCount ); } /// - /// 保存集成事件日志记录 + /// 获取集成事件计数 /// - /// 集成事件日志记录 - /// 取消令牌 - public virtual async Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) { - await StateManage.SaveAsync( eventLog, cancellationToken ); + protected async Task GetIntegrationEventCount( CancellationToken cancellationToken ) { + return await StateManager.GetAsync( KeyCount,cancellationToken ) ?? new IntegrationEventCount(); + } + + /// + public virtual async Task GetCountAsync( CancellationToken cancellationToken = default ) { + var result = await GetIntegrationEventCount( cancellationToken ); + return result.Count.SafeValue(); + } + + /// + public virtual async Task ClearCountAsync( CancellationToken cancellationToken = default ) { + await StateManager.RemoveAsync( KeyCount, cancellationToken ); } } \ No newline at end of file diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs index 84734adc2..3ffe00cb0 100644 --- a/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs +++ b/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs @@ -53,12 +53,48 @@ public IntegrationEventManager( IIntegrationEventLogStore store, Util.Sessions.I #endregion + #region IncrementAsync + + /// + public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) { + try { + await Store.IncrementAsync( cancellationToken ); + } + catch ( ConcurrencyException ) { + Log.LogDebug( "更新集成事件计数出现并发异常,即将重试" ); + await IncrementAsync( cancellationToken ); + } + catch ( Exception exception ) { + Log.LogError( exception, "更新集成事件计数失败" ); + } + } + + #endregion + + #region GetCountAsync + + /// + public virtual async Task GetCountAsync( CancellationToken cancellationToken = default ) { + return await Store.GetCountAsync( cancellationToken ); + } + + #endregion + + #region ClearCountAsync + + /// + public virtual async Task ClearCountAsync( CancellationToken cancellationToken = default ) { + await Store.ClearCountAsync( cancellationToken ); + } + + #endregion + #region GetAsync /// public virtual async Task GetAsync( string eventId, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; return await Store.GetAsync( eventId, cancellationToken ); } @@ -148,10 +184,10 @@ public virtual bool IsSubscriptionSuccess( IntegrationEventLog eventLog ) { /// public virtual async Task CreatePublishLogAsync( PubsubArgument argument, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; var result = CreateEventLog( argument ); if ( result == null ) - return null; + return NullIntegrationEventLog.Instance; await SaveAsync( result, cancellationToken ); Log.LogDebug( "创建集成事件发布日志记录成功,EventLog={@EventLog}", result ); return result; @@ -189,10 +225,10 @@ protected virtual IntegrationEventLog CreateEventLog( PubsubArgument argument ) /// public virtual async Task CreateSubscriptionLogAsync( string eventId, string routeUrl, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; if ( eventId.IsEmpty() ) { Log.LogWarning( "创建集成事件订阅日志记录失败,事件标识不能为空,routeUrl={@routeUrl}", routeUrl ); - return null; + return NullIntegrationEventLog.Instance; } var eventLog = await GetAsync( eventId, cancellationToken ); return await CreateSubscriptionLogAsync( eventLog, routeUrl, cancellationToken ); @@ -201,7 +237,7 @@ protected virtual IntegrationEventLog CreateEventLog( PubsubArgument argument ) /// public virtual async Task CreateSubscriptionLogAsync( IntegrationEventLog eventLog, string routeUrl, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; eventLog.CheckNull( nameof( eventLog ) ); if ( CanSubscription( eventLog ) == false ) return eventLog; @@ -293,10 +329,10 @@ protected virtual void UpdateRetryTime( SubscriptionLog subscriptionLog ) { /// public virtual async Task SubscriptionSuccessAsync( string eventId, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; if ( eventId.IsEmpty() ) { Log.LogWarning( "SubscriptionSuccessAsync更新集成事件订阅日志记录失败,事件标识不能为空." ); - return null; + return NullIntegrationEventLog.Instance; } var eventLog = await GetAsync( eventId, cancellationToken ); return await SubscriptionSuccessAsync( eventLog, cancellationToken ); @@ -309,13 +345,13 @@ protected virtual void UpdateRetryTime( SubscriptionLog subscriptionLog ) { /// 取消令牌 public virtual async Task SubscriptionSuccessAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; eventLog.CheckNull( nameof( eventLog ) ); var appId = GetAppId( eventLog.Id ); var subscriptionLog = GetSubscriptionLog( eventLog, appId ); if ( subscriptionLog == null ) { Log.LogWarning( "更新集成事件订阅日志记录失败,未找到订阅日志记录,appId={appId},EventLog={@EventLog}", appId,ToDebugEventLog( eventLog ) ); - return null; + return NullIntegrationEventLog.Instance; } subscriptionLog.State = SubscriptionState.Success; subscriptionLog.LastModificationTime = Time.Now; @@ -362,10 +398,10 @@ protected void UpdateEventLogState( IntegrationEventLog eventLog ) { /// 取消令牌 public virtual async Task SubscriptionFailAsync( string eventId, string message, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; if ( eventId.IsEmpty() ) { Log.LogWarning( "SubscriptionFailAsync更新集成事件订阅日志记录失败,事件标识不能为空." ); - return null; + return NullIntegrationEventLog.Instance; } var eventLog = await GetAsync( eventId, cancellationToken ); return await SubscriptionFailAsync( eventLog, message, cancellationToken ); @@ -379,13 +415,13 @@ protected void UpdateEventLogState( IntegrationEventLog eventLog ) { /// 取消令牌 public virtual async Task SubscriptionFailAsync( IntegrationEventLog eventLog, string message, CancellationToken cancellationToken = default ) { if ( Options.Pubsub.EnableEventLog == false ) - return new IntegrationEventLog(); + return NullIntegrationEventLog.Instance; eventLog.CheckNull( nameof( eventLog ) ); var appId = GetAppId( eventLog.Id ); var subscriptionLog = GetSubscriptionLog( eventLog, appId ); if ( subscriptionLog == null ) { Log.LogWarning( "更新集成事件订阅日志记录失败,未找到订阅日志记录,appId={appId},EventLog={@EventLog}", appId, ToDebugEventLog( eventLog ) ); - return null; + return NullIntegrationEventLog.Instance; } subscriptionLog.State = SubscriptionState.Fail; subscriptionLog.LastModificationTime = Time.Now; diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs similarity index 55% rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs index 5aa79415a..d86ce746d 100644 --- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs +++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs @@ -3,7 +3,7 @@ /// /// Dapr状态管理 /// -public class DaprStateManage : DaprStateManageBase, IStateManage { +public class DaprStateManager : DaprStateManagerBase, IStateManager { /// /// 初始化Dapr状态管理 /// @@ -11,36 +11,36 @@ public class DaprStateManage : DaprStateManageBase, IStateManage { /// Dapr配置 /// 日志工厂 /// 状态存储键生成器 - public DaprStateManage( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) + public DaprStateManager( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) : base(client,options,loggerFactory,keyGenerator){ } /// - public IStateManage OrderBy( Expression> expression ) { + public IStateManager OrderBy( Expression> expression ) { Sort.OrderBy( expression ); return this; } /// - public IStateManage OrderByDescending( Expression> expression ) { + public IStateManager OrderByDescending( Expression> expression ) { Sort.OrderByDescending( expression ); return this; } /// - public IStateManage Equal( Expression> expression, object value ) { + public IStateManager Equal( Expression> expression, object value ) { Filter.Equal( expression, value ); return this; } /// - public IStateManage In( Expression> expression, IEnumerable values ) { + public IStateManager In( Expression> expression, IEnumerable values ) { Filter.In( expression, values ); return this; } /// - public IStateManage In( Expression> expression, params object[] values ) { + public IStateManager In( Expression> expression, params object[] values ) { Filter.In( expression, values ); return this; } diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.Query.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.Query.cs similarity index 84% rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.Query.cs rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.Query.cs index d707ea7d0..b883ed679 100644 --- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.Query.cs +++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.Query.cs @@ -7,7 +7,7 @@ namespace Util.Microservices.Dapr.StateManagements; /// /// Dapr状态管理 - 查询 /// -public partial class DaprStateManageBase { +public partial class DaprStateManagerBase { #region Limit @@ -15,7 +15,7 @@ public partial class DaprStateManageBase { /// 添加分页大小 /// /// 分页大小 - public TStateManage Limit( int pageSize ) { + public TStateManager Limit( int pageSize ) { Page.Limit = pageSize; return Return(); } @@ -28,7 +28,7 @@ public TStateManage Limit( int pageSize ) { /// 添加迭代令牌 /// /// 迭代令牌 - public TStateManage Token( int token ) { + public TStateManager Token( int token ) { Page.Token = token.ToString(); return Return(); } @@ -38,7 +38,7 @@ public TStateManage Token( int token ) { #region OrderBy /// - public TStateManage OrderBy( string orderBy ) { + public TStateManager OrderBy( string orderBy ) { Sort.OrderBy( orderBy ); return Return(); } @@ -48,7 +48,7 @@ public TStateManage OrderBy( string orderBy ) { #region Equal /// - public TStateManage Equal( string property, object value ) { + public TStateManager Equal( string property, object value ) { Filter.Equal( property, value ); return Return(); } @@ -58,7 +58,7 @@ public TStateManage Equal( string property, object value ) { #region EqualIf /// - public TStateManage EqualIf( string property, object value, bool condition ) { + public TStateManager EqualIf( string property, object value, bool condition ) { return condition ? Equal( property, value ) : Return(); } @@ -67,13 +67,13 @@ public TStateManage EqualIf( string property, object value, bool condition ) { #region In /// - public TStateManage In( string property, IEnumerable values ) { + public TStateManager In( string property, IEnumerable values ) { Filter.In( property, values ); return Return(); } /// - public TStateManage In( string property, params object[] values ) { + public TStateManager In( string property, params object[] values ) { Filter.In( property, values ); return Return(); } @@ -83,7 +83,7 @@ public TStateManage In( string property, params object[] values ) { #region And /// - public TStateManage And( params IStateCondition[] conditions ) { + public TStateManager And( params IStateCondition[] conditions ) { if ( conditions == null ) return Return(); foreach ( var condition in conditions ) @@ -96,7 +96,7 @@ public TStateManage And( params IStateCondition[] conditions ) { #region Or /// - public TStateManage Or( params IStateCondition[] conditions ) { + public TStateManager Or( params IStateCondition[] conditions ) { if ( conditions == null ) return Return(); foreach ( var condition in conditions ) @@ -191,6 +191,19 @@ public TStateManage Or( params IStateCondition[] conditions ) { #endregion + #region SingleAsync + + /// + public virtual async Task SingleAsync( CancellationToken cancellationToken = default ) where TValue : IDataKey { + SetDataTypeCondition(); + Page.Limit = 1; + var result = await QueryAsync( cancellationToken ); + var value = result.FirstOrDefault(); + return await GetByIdAsync( value?.Id, cancellationToken ); + } + + #endregion + #region GetAllAsync /// diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs similarity index 90% rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs index 01d8e4ff4..4e8bae550 100644 --- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs +++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs @@ -6,7 +6,7 @@ namespace Util.Microservices.Dapr.StateManagements; /// /// Dapr状态管理 /// -public partial class DaprStateManageBase : IStateManageBase where TStateManage : IStateManageBase { +public partial class DaprStateManagerBase : IStateManagerBase where TStateManager : IStateManagerBase { #region 字段 @@ -74,10 +74,10 @@ public partial class DaprStateManageBase : IStateManageBaseDapr配置 /// 日志工厂 /// 状态存储键生成器 - public DaprStateManageBase( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) { + public DaprStateManagerBase( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) { Client = client ?? throw new ArgumentNullException( nameof( client ) ); Options = options?.Value ?? new DaprOptions(); - Logger = loggerFactory?.CreateLogger( typeof( DaprStateManage ) ) ?? NullLogger.Instance; + Logger = loggerFactory?.CreateLogger( typeof( DaprStateManager ) ) ?? NullLogger.Instance; KeyGenerator = keyGenerator ?? throw new ArgumentNullException( nameof( keyGenerator ) ); Metadatas = new Dictionary(); ConsistencyMode = ConsistencyMode.Eventual; @@ -94,8 +94,8 @@ public DaprStateManageBase( DaprClient client, IOptions options, IL /// /// 返回 /// - private TStateManage Return() { - return (TStateManage)(object)this; + private TStateManager Return() { + return (TStateManager)(object)this; } /// @@ -147,7 +147,7 @@ protected void SetDataTypeCondition() { #region StoreName /// - public TStateManage StoreName( string storeName ) { + public TStateManager StoreName( string storeName ) { _storeName = storeName; return Return(); } @@ -157,7 +157,7 @@ public TStateManage StoreName( string storeName ) { #region Clear /// - public TStateManage Clear() { + public TStateManager Clear() { Metadatas.Clear(); ConsistencyMode = ConsistencyMode.Eventual; StateTransactionRequests.Clear(); @@ -180,7 +180,7 @@ protected void ClearQuery() { #region BeginTransaction /// - public TStateManage BeginTransaction() { + public TStateManager BeginTransaction() { IsTransaction = true; return Return(); } @@ -190,7 +190,7 @@ public TStateManage BeginTransaction() { #region JsonSerializerOptions /// - public TStateManage JsonSerializerOptions( JsonSerializerOptions options ) { + public TStateManager JsonSerializerOptions( JsonSerializerOptions options ) { SerializerOptions = options; return Return(); } @@ -204,7 +204,7 @@ public TStateManage JsonSerializerOptions( JsonSerializerOptions options ) { /// /// 键 /// 值 - public TStateManage Metadata( string key, string value ) { + public TStateManager Metadata( string key, string value ) { if ( key.IsEmpty() ) return Return(); if ( value.IsEmpty() ) @@ -218,7 +218,7 @@ public TStateManage Metadata( string key, string value ) { /// 设置元数据 /// /// 元数据键值对集合 - public TStateManage Metadata( IDictionary metadata ) { + public TStateManager Metadata( IDictionary metadata ) { if ( metadata == null ) return Return(); foreach ( var item in metadata ) @@ -234,7 +234,7 @@ public TStateManage Metadata( IDictionary metadata ) { /// 移除元数据 /// /// 键 - public TStateManage RemoveMetadata( string key ) { + public TStateManager RemoveMetadata( string key ) { if ( key.IsEmpty() ) return Return(); if ( Metadatas.ContainsKey( key ) ) @@ -247,7 +247,7 @@ public TStateManage RemoveMetadata( string key ) { #region ContentType /// - public TStateManage ContentType( string type ) { + public TStateManager ContentType( string type ) { Metadata( "contentType", type ); return Return(); } @@ -259,7 +259,7 @@ public TStateManage ContentType( string type ) { /// /// 设置contentType为application/json /// - public TStateManage JsonType() { + public TStateManager JsonType() { return ContentType( "application/json" ); } diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs similarity index 55% rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs index 8ee99901c..9c39c2e8d 100644 --- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs +++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs @@ -3,7 +3,7 @@ /// /// Dapr状态管理 /// -public class DaprStateManage : DaprStateManageBase>, IStateManage { +public class DaprStateManager : DaprStateManagerBase>, IStateManager { /// /// 初始化Dapr状态管理 /// @@ -11,36 +11,36 @@ public class DaprStateManage : DaprStateManageBase>, IStateMa /// Dapr配置 /// 日志工厂 /// 状态存储键生成器 - public DaprStateManage( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) + public DaprStateManager( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) : base( client, options, loggerFactory, keyGenerator ) { } /// - public IStateManage OrderBy( Expression> expression ) { + public IStateManager OrderBy( Expression> expression ) { Sort.OrderBy( expression ); return this; } /// - public IStateManage OrderByDescending( Expression> expression ) { + public IStateManager OrderByDescending( Expression> expression ) { Sort.OrderByDescending( expression ); return this; } /// - public IStateManage Equal( Expression> expression, object value ) { + public IStateManager Equal( Expression> expression, object value ) { Filter.Equal( expression, value ); return this; } /// - public IStateManage In( Expression> expression, IEnumerable values ) { + public IStateManager In( Expression> expression, IEnumerable values ) { Filter.In( expression, values ); return this; } /// - public IStateManage In( Expression> expression, params object[] values ) { + public IStateManager In( Expression> expression, params object[] values ) { Filter.In( expression, values ); return this; } diff --git a/src/Util.Microservices.Dapr/StateManageExtensions.cs b/src/Util.Microservices.Dapr/StateManagerExtensions.cs similarity index 70% rename from src/Util.Microservices.Dapr/StateManageExtensions.cs rename to src/Util.Microservices.Dapr/StateManagerExtensions.cs index 0fd4194b1..b7a8f49ee 100644 --- a/src/Util.Microservices.Dapr/StateManageExtensions.cs +++ b/src/Util.Microservices.Dapr/StateManagerExtensions.cs @@ -3,7 +3,7 @@ /// /// 状态管理操作扩展 /// -public static class StateManageExtensions { +public static class StateManagerExtensions { #region EqualIfNotEmpty @@ -13,7 +13,7 @@ public static class StateManageExtensions { /// 状态管理操作 /// 属性名 /// 属性值,如果为空,则忽略该查询条件 - public static TStateManage EqualIfNotEmpty( this TStateManage source, string property, object value ) where TStateManage : IStateManageBase { + public static TStateManager EqualIfNotEmpty( this TStateManager source, string property, object value ) where TStateManager : IStateManagerBase { source.CheckNull( nameof( source ) ); return value.SafeString().IsEmpty() ? source : source.Equal( property, value ); } @@ -29,7 +29,7 @@ public static TStateManage EqualIfNotEmpty( this TStateManage sour /// 属性名 /// 属性值 /// 该值为true时添加查询条件,否则忽略 - public static TStateManage InIf( this TStateManage source, string property, IEnumerable values, bool condition ) where TStateManage : IStateManageBase { + public static TStateManager InIf( this TStateManager source, string property, IEnumerable values, bool condition ) where TStateManager : IStateManagerBase { source.CheckNull( nameof( source ) ); return condition ? source.In( property, values ) : source; } diff --git a/src/Util.Microservices/Events/IIntegrationEventLogStore.cs b/src/Util.Microservices/Events/IIntegrationEventLogStore.cs index 2b170247c..d5cc3b457 100644 --- a/src/Util.Microservices/Events/IIntegrationEventLogStore.cs +++ b/src/Util.Microservices/Events/IIntegrationEventLogStore.cs @@ -16,4 +16,19 @@ public interface IIntegrationEventLogStore : ITransientDependency { /// 集成事件日志记录 /// 取消令牌 Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ); + /// + /// 集成事件总条数加1 + /// + /// 取消令牌 + Task IncrementAsync( CancellationToken cancellationToken = default ); + /// + /// 获取集成事件总条数 + /// + /// 取消令牌 + Task GetCountAsync( CancellationToken cancellationToken = default ); + /// + /// 清空集成事件总条数 + /// + /// 取消令牌 + Task ClearCountAsync( CancellationToken cancellationToken = default ); } \ No newline at end of file diff --git a/src/Util.Microservices/Events/IIntegrationEventManager.cs b/src/Util.Microservices/Events/IIntegrationEventManager.cs index 98e03eed6..ca1ca66b9 100644 --- a/src/Util.Microservices/Events/IIntegrationEventManager.cs +++ b/src/Util.Microservices/Events/IIntegrationEventManager.cs @@ -4,6 +4,21 @@ /// 集成事件管理器 /// public interface IIntegrationEventManager : ITransientDependency { + /// + /// 集成事件总条数加1 + /// + /// 取消令牌 + Task IncrementAsync( CancellationToken cancellationToken = default ); + /// + /// 获取集成事件总条数 + /// + /// 取消令牌 + Task GetCountAsync( CancellationToken cancellationToken = default ); + /// + /// 清空集成事件总条数 + /// + /// 取消令牌 + Task ClearCountAsync( CancellationToken cancellationToken = default ); /// /// 获取集成事件日志记录 /// diff --git a/src/Util.Microservices/Events/IntegrationEventLog.cs b/src/Util.Microservices/Events/IntegrationEventLog.cs index b5e76dd21..9916dba79 100644 --- a/src/Util.Microservices/Events/IntegrationEventLog.cs +++ b/src/Util.Microservices/Events/IntegrationEventLog.cs @@ -1,5 +1,15 @@ namespace Util.Microservices.Events; +/// +/// 空集成事件日志记录 +/// +public class NullIntegrationEventLog : IntegrationEventLog { + /// + /// 空集成事件日志记录实例 + /// + public static readonly IntegrationEventLog Instance = new NullIntegrationEventLog(); +} + /// /// 集成事件日志记录 /// diff --git a/src/Util.Microservices/IMicroserviceClient.cs b/src/Util.Microservices/IMicroserviceClient.cs index 3b91559b7..7ee1e0ef0 100644 --- a/src/Util.Microservices/IMicroserviceClient.cs +++ b/src/Util.Microservices/IMicroserviceClient.cs @@ -22,5 +22,5 @@ public interface IMicroserviceClient { /// /// 状态管理 /// - IStateManage StateManage { get; } + IStateManager StateManager { get; } } \ No newline at end of file diff --git a/src/Util.Microservices/IStateManage.cs b/src/Util.Microservices/IStateManager.cs similarity index 60% rename from src/Util.Microservices/IStateManage.cs rename to src/Util.Microservices/IStateManager.cs index 9cfc7fc35..7a57488f1 100644 --- a/src/Util.Microservices/IStateManage.cs +++ b/src/Util.Microservices/IStateManager.cs @@ -3,67 +3,67 @@ /// /// 状态管理 /// -public interface IStateManage : IStateManageBase { +public interface IStateManager : IStateManagerBase { /// /// 设置升序排序属性 /// /// 属性表达式 - IStateManage OrderBy( Expression> expression ); + IStateManager OrderBy( Expression> expression ); /// /// 设置降序排序属性 /// /// 属性表达式 - IStateManage OrderByDescending( Expression> expression ); + IStateManager OrderByDescending( Expression> expression ); /// /// 设置相等条件 /// /// 属性表达式 /// 属性值 - IStateManage Equal( Expression> expression, object value ); + IStateManager Equal( Expression> expression, object value ); /// /// 设置In条件 /// /// 属性表达式 /// 属性值 - IStateManage In( Expression> expression, IEnumerable values ); + IStateManager In( Expression> expression, IEnumerable values ); /// /// 设置In条件 /// /// 属性表达式 /// 属性值 - IStateManage In( Expression> expression, params object[] values ); + IStateManager In( Expression> expression, params object[] values ); } /// /// 状态管理 /// -public interface IStateManage : IStateManageBase> { +public interface IStateManager : IStateManagerBase> { /// /// 设置升序排序属性 /// /// 属性表达式 - IStateManage OrderBy( Expression> expression ); + IStateManager OrderBy( Expression> expression ); /// /// 设置降序排序属性 /// /// 属性表达式 - IStateManage OrderByDescending( Expression> expression ); + IStateManager OrderByDescending( Expression> expression ); /// /// 设置相等条件 /// /// 属性名表达式 /// 属性值 - IStateManage Equal( Expression> expression, object value ); + IStateManager Equal( Expression> expression, object value ); /// /// 设置In条件 /// /// 属性名表达式 /// 属性值 - IStateManage In( Expression> expression, IEnumerable values ); + IStateManager In( Expression> expression, IEnumerable values ); /// /// 设置In条件 /// /// 属性名表达式 /// 属性值 - IStateManage In( Expression> expression, params object[] values ); + IStateManager In( Expression> expression, params object[] values ); } \ No newline at end of file diff --git a/src/Util.Microservices/IStateManageBase.cs b/src/Util.Microservices/IStateManagerBase.cs similarity index 82% rename from src/Util.Microservices/IStateManageBase.cs rename to src/Util.Microservices/IStateManagerBase.cs index d80119e92..1ebb45ea9 100644 --- a/src/Util.Microservices/IStateManageBase.cs +++ b/src/Util.Microservices/IStateManagerBase.cs @@ -5,96 +5,96 @@ namespace Util.Microservices; /// /// 状态管理 /// -public interface IStateManageBase : ITransientDependency where TStateManage : IStateManageBase { +public interface IStateManagerBase : ITransientDependency where TStateManager : IStateManagerBase { /// /// 设置状态组件名称 /// /// 状态组件名称 - TStateManage StoreName( string storeName ); + TStateManager StoreName( string storeName ); /// /// 清理 /// - TStateManage Clear(); + TStateManager Clear(); /// /// 开始事务 /// - TStateManage BeginTransaction(); + TStateManager BeginTransaction(); /// /// 设置Json序列化配置 /// /// Json序列化配置 - TStateManage JsonSerializerOptions( JsonSerializerOptions options ); + TStateManager JsonSerializerOptions( JsonSerializerOptions options ); /// /// 设置元数据 /// /// 键 /// 值 - TStateManage Metadata( string key, string value ); + TStateManager Metadata( string key, string value ); /// /// 设置元数据 /// /// 元数据键值对集合 - TStateManage Metadata( IDictionary metadata ); + TStateManager Metadata( IDictionary metadata ); /// /// 移除元数据 /// /// 键 - TStateManage RemoveMetadata( string key ); + TStateManager RemoveMetadata( string key ); /// /// 设置内容类型: contentType /// /// 内容类型 - TStateManage ContentType( string type ); + TStateManager ContentType( string type ); /// /// 添加分页大小 /// /// 分页大小 - TStateManage Limit( int pageSize ); + TStateManager Limit( int pageSize ); /// /// 添加迭代令牌,即分页跳过行数 /// /// 迭代令牌,即分页跳过行数 - TStateManage Token( int token ); + TStateManager Token( int token ); /// /// 添加排序条件 /// /// 排序条件,范例: a,b desc - TStateManage OrderBy( string orderBy ); + TStateManager OrderBy( string orderBy ); /// /// 设置相等条件 /// /// 属性名 /// 属性值 - TStateManage Equal( string property, object value ); + TStateManager Equal( string property, object value ); /// /// 根据规则添加相等查询条件 /// /// 属性名 /// 属性值 /// 该值为true时添加查询条件,否则忽略 - TStateManage EqualIf( string property, object value, bool condition ); + TStateManager EqualIf( string property, object value, bool condition ); /// /// 设置In条件 /// /// 属性名 /// 属性值 - TStateManage In( string property, IEnumerable values ); + TStateManager In( string property, IEnumerable values ); /// /// 设置In条件 /// /// 属性名 /// 属性值 - TStateManage In( string property, params object[] values ); + TStateManager In( string property, params object[] values ); /// /// 设置And连接条件 /// /// 属性名 - TStateManage And( params IStateCondition[] conditions ); + TStateManager And( params IStateCondition[] conditions ); /// /// 设置Or连接条件 /// /// 属性名 - TStateManage Or( params IStateCondition[] conditions ); + TStateManager Or( params IStateCondition[] conditions ); /// /// 添加数据 /// @@ -182,6 +182,12 @@ public interface IStateManageBase : ITransientDependency where /// 取消令牌 Task GetByIdAsync( string id, CancellationToken cancellationToken = default ) where TValue : IDataKey; /// + /// 获取指定类型的单条数据 + /// + /// 值类型 + /// 取消令牌 + Task SingleAsync( CancellationToken cancellationToken = default ) where TValue : IDataKey; + /// /// 获取指定类型的全部数据 /// /// 值类型 diff --git a/src/Util.Microservices/PubsubNameAttribute.cs b/src/Util.Microservices/PubsubNameAttribute.cs new file mode 100644 index 000000000..ac9d4d787 --- /dev/null +++ b/src/Util.Microservices/PubsubNameAttribute.cs @@ -0,0 +1,38 @@ +namespace Util.Microservices; + +/// +/// 发布订阅名称 +/// +public class PubsubNameAttribute : Attribute { + /// + /// 发布订阅名称 + /// + public string Name { get; } + + /// + /// 初始化发布订阅名称 + /// + /// 发布订阅名称 + public PubsubNameAttribute( string name ) { + Name = name; + } + + /// + /// 获取发布订阅名称,未设置则返回默认值: pubsub + /// + /// 类型 + public static string GetName() { + return GetName( typeof( T ) ); + } + + /// + /// 获取发布订阅名称 + /// + /// 类型 + public static string GetName( Type type ) { + if ( type == null ) + return null; + var attribute = type.GetCustomAttribute(); + return attribute == null || attribute.Name.IsEmpty() ? "pubsub" : attribute.Name; + } +} \ No newline at end of file diff --git a/src/Util.Microservices/TopicNameAttribute.cs b/src/Util.Microservices/TopicNameAttribute.cs new file mode 100644 index 000000000..e1c16bc2b --- /dev/null +++ b/src/Util.Microservices/TopicNameAttribute.cs @@ -0,0 +1,38 @@ +namespace Util.Microservices; + +/// +/// 事件主题名称 +/// +public class TopicNameAttribute : Attribute { + /// + /// 事件主题名称 + /// + public string Name { get; } + + /// + /// 初始化事件主题名称 + /// + /// 事件主题名称 + public TopicNameAttribute( string name ) { + Name = name; + } + + /// + /// 获取事件主题名称 + /// + /// 类型 + public static string GetName() { + return GetName( typeof( T ) ); + } + + /// + /// 获取事件主题名称 + /// + /// 类型 + public static string GetName( Type type ) { + if ( type == null ) + return null; + var attribute = type.GetCustomAttribute(); + return attribute == null || attribute.Name.IsEmpty() ? type.Name : attribute.Name; + } +} \ No newline at end of file diff --git a/src/Util.Microservices/Usings.cs b/src/Util.Microservices/Usings.cs index 6982fdf75..6763120d6 100644 --- a/src/Util.Microservices/Usings.cs +++ b/src/Util.Microservices/Usings.cs @@ -6,6 +6,7 @@ global using System.Text.Json; global using System.Linq.Expressions; global using System.ComponentModel; +global using System.Reflection; global using Microsoft.AspNetCore.Http; global using Util.Dependency; global using Util.Data; \ No newline at end of file diff --git a/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs b/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs index 6bb004283..49367c961 100644 --- a/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs +++ b/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs @@ -1,11 +1,18 @@ -namespace Util.Security.Authorization; +using System.Threading; + +namespace Util.Security.Authorization; /// /// 默认权限管理器 /// public class DefaultPermissionManager : IPermissionManager { /// - public Task HasPermissionAsync( string resourceUri ) { + public bool HasPermission( string resourceUri ) { + return true; + } + + /// + public Task HasPermissionAsync( string resourceUri, CancellationToken cancellationToken = default ) { return Task.FromResult( true ); } } \ No newline at end of file diff --git a/src/Util.Security/Security/Authorization/IPermissionManager.cs b/src/Util.Security/Security/Authorization/IPermissionManager.cs index ea87cb9c4..d4b9fc758 100644 --- a/src/Util.Security/Security/Authorization/IPermissionManager.cs +++ b/src/Util.Security/Security/Authorization/IPermissionManager.cs @@ -1,4 +1,6 @@ -namespace Util.Security.Authorization; +using System.Threading; + +namespace Util.Security.Authorization; /// /// 权限管理器 @@ -8,5 +10,11 @@ public interface IPermissionManager { /// 检查当前用户是否具有该资源的访问权限,返回true表示允许访问,false表示拒绝访问 /// /// 资源标识 - Task HasPermissionAsync( string resourceUri ); + bool HasPermission( string resourceUri ); + /// + /// 检查当前用户是否具有该资源的访问权限,返回true表示允许访问,false表示拒绝访问 + /// + /// 资源标识 + /// 取消令牌 + Task HasPermissionAsync( string resourceUri, CancellationToken cancellationToken = default ); } \ No newline at end of file diff --git a/src/Util.Security/Security/ClaimTypes.cs b/src/Util.Security/Security/ClaimTypes.cs index 29cdbab6d..08ccf5204 100644 --- a/src/Util.Security/Security/ClaimTypes.cs +++ b/src/Util.Security/Security/ClaimTypes.cs @@ -37,14 +37,6 @@ public static class ClaimTypes { /// public static string TenantId { get; set; } = "tenant_id"; /// - /// tenant_code,租户编码 - /// - public static string TenantCode { get; set; } = "tenant_code"; - /// - /// tenant_name,租户名称 - /// - public static string TenantName { get; set; } = "tenant_name"; - /// /// role,角色 /// public static string Role { get; set; } = "role"; diff --git a/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj b/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj index 189a5f499..47ecdc330 100644 --- a/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj +++ b/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj @@ -38,6 +38,6 @@ - + diff --git a/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj b/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj new file mode 100644 index 000000000..4de393cfc --- /dev/null +++ b/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj @@ -0,0 +1,34 @@ + + + + $(NetTargetFramework) + icon.jpg + Util.Tenants.Abstractions + Util.Tenants + Util.Tenants.Abstractions是Util应用框架多租户接口定义类库 + + + + + .\obj\Debug\$(NetTargetFramework)\Util.Tenants.Abstractions.xml + + + + + .\obj\Release\$(NetTargetFramework)\Util.Tenants.Abstractions.xml + + + + + True + False + + + + + + + + + + diff --git a/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs b/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs new file mode 100644 index 000000000..ae37c3e09 --- /dev/null +++ b/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs @@ -0,0 +1,20 @@ +namespace Util.Tenants; + +/// +/// 切换租户管理器 +/// +public interface ISwitchTenantManager : ITransientDependency { + /// + /// 获取切换的租户标识 + /// + string GetSwitchTenantId(); + /// + /// 切换租户标识 + /// + /// 租户标识 + Task SwitchTenantAsync( string tenantId ); + /// + /// 重置租户标识 + /// + Task ResetTenantAsync(); +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/ITenant.cs b/src/Util.Tenants.Abstractions/ITenant.cs new file mode 100644 index 000000000..38e4b5ca4 --- /dev/null +++ b/src/Util.Tenants.Abstractions/ITenant.cs @@ -0,0 +1,11 @@ +namespace Util.Tenants; + +/// +/// 租户 +/// +public interface ITenant { + /// + /// 租户标识 + /// + string TenantId { get; set; } +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/ITenantManager.cs b/src/Util.Tenants.Abstractions/ITenantManager.cs new file mode 100644 index 000000000..88220611f --- /dev/null +++ b/src/Util.Tenants.Abstractions/ITenantManager.cs @@ -0,0 +1,31 @@ +namespace Util.Tenants; + +/// +/// 租户管理器 +/// +public interface ITenantManager : IScopeDependency { + /// + /// 是否启用多租户架构 + /// + bool Enabled(); + /// + /// 是否允许使用多数据库 + /// + bool AllowMultipleDatabase(); + /// + /// 是否平台供应商 + /// + bool IsHost(); + /// + /// 获取当前租户标识 + /// + string GetTenantId(); + /// + /// 获取当前租户配置 + /// + TenantInfo GetTenant(); + /// + /// 是否禁用租户过滤器 + /// + bool IsDisableTenantFilter(); +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/ITenantStore.cs b/src/Util.Tenants.Abstractions/ITenantStore.cs new file mode 100644 index 000000000..7442d1744 --- /dev/null +++ b/src/Util.Tenants.Abstractions/ITenantStore.cs @@ -0,0 +1,12 @@ +namespace Util.Tenants; + +/// +/// 租户存储器 +/// +public interface ITenantStore : IScopeDependency { + /// + /// 获取当前租户配置 + /// + /// 租户标识 + TenantInfo GetTenant( string tenantId ); +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs b/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs new file mode 100644 index 000000000..b4ebd6132 --- /dev/null +++ b/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs @@ -0,0 +1,19 @@ +namespace Util.Tenants; + +/// +/// 查看租户管理器 +/// +public interface IViewAllTenantManager : ITransientDependency { + /// + /// 是否禁用租户过滤器 + /// + bool IsDisableTenantFilter(); + /// + /// 启用查看所有租户数据,仅对单一数据库模式有效 + /// + Task EnableViewAllAsync(); + /// + /// 禁用查看所有租户数据,仅对单一数据库模式有效 + /// + Task DisableViewAllAsync(); +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/NullTenantManager.cs b/src/Util.Tenants.Abstractions/NullTenantManager.cs new file mode 100644 index 000000000..35983690f --- /dev/null +++ b/src/Util.Tenants.Abstractions/NullTenantManager.cs @@ -0,0 +1,42 @@ +namespace Util.Tenants; + +/// +/// 空租户管理器 +/// +[Ioc( -9 )] +public class NullTenantManager : ITenantManager { + /// + /// 租户管理器空实例 + /// + public static readonly ITenantManager Instance = new NullTenantManager(); + + /// + public bool Enabled() { + return false; + } + + /// + public bool AllowMultipleDatabase() { + return false; + } + + /// + public bool IsHost() { + return false; + } + + /// + public bool IsDisableTenantFilter() { + return false; + } + + /// + public string GetTenantId() { + return null; + } + + /// + public TenantInfo GetTenant() { + return null; + } +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/NullTenantStore.cs b/src/Util.Tenants.Abstractions/NullTenantStore.cs new file mode 100644 index 000000000..61372cbc0 --- /dev/null +++ b/src/Util.Tenants.Abstractions/NullTenantStore.cs @@ -0,0 +1,20 @@ +namespace Util.Tenants; + +/// +/// 空租户存储器 +/// +[Ioc(-9)] +public class NullTenantStore : ITenantStore { + /// + /// 空租户存储器 + /// + public static readonly ITenantStore Instance = new NullTenantStore(); + + /// + /// 获取当前租户配置 + /// + /// 租户标识 + public TenantInfo GetTenant( string tenantId ) { + return null; + } +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/TenantInfo.cs b/src/Util.Tenants.Abstractions/TenantInfo.cs new file mode 100644 index 000000000..46c00811a --- /dev/null +++ b/src/Util.Tenants.Abstractions/TenantInfo.cs @@ -0,0 +1,17 @@ +using Util.Data; + +namespace Util.Tenants; + +/// +/// 租户信息 +/// +public class TenantInfo { + /// + /// 租户标识 + /// + public string TenantId { get; set; } + /// + /// 连接字符串集合 + /// + public ConnectionStringCollection ConnectionStrings { get; set; } +} \ No newline at end of file diff --git a/src/Util.Tenants.Abstractions/Usings.cs b/src/Util.Tenants.Abstractions/Usings.cs new file mode 100644 index 000000000..5c84008de --- /dev/null +++ b/src/Util.Tenants.Abstractions/Usings.cs @@ -0,0 +1,10 @@ +global using System; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; +global using System.Text; +global using System.Reflection; +global using System.Linq.Expressions; +global using System.ComponentModel.DataAnnotations; +global using Util.Dependency; \ No newline at end of file diff --git a/src/Util.Tenants/02-Util.Tenants.csproj b/src/Util.Tenants/02-Util.Tenants.csproj new file mode 100644 index 000000000..38beea97e --- /dev/null +++ b/src/Util.Tenants/02-Util.Tenants.csproj @@ -0,0 +1,34 @@ + + + + $(NetTargetFramework) + icon.jpg + Util.Tenants + Util.Tenants + Util.Tenants是Util应用框架多租户实现类库 + + + + + .\obj\Debug\$(NetTargetFramework)\Util.Tenants.xml + + + + + .\obj\Release\$(NetTargetFramework)\Util.Tenants.xml + + + + + True + False + + + + + + + + + + diff --git a/src/Util.Tenants/AppBuilderExtensions.cs b/src/Util.Tenants/AppBuilderExtensions.cs new file mode 100644 index 000000000..8fc8229a3 --- /dev/null +++ b/src/Util.Tenants/AppBuilderExtensions.cs @@ -0,0 +1,36 @@ +using Util.Configs; +using Util.Tenants.Resolvers; + +namespace Util.Tenants; + +/// +/// 租户操作扩展 +/// +public static class AppBuilderExtensions { + /// + /// 配置租户操作 + /// + /// 应用生成器 + public static IAppBuilder AddTenant( this IAppBuilder builder ) { + return builder.AddTenant( null ); + } + + /// + /// 配置租户操作 + /// + /// 应用生成器 + /// 租户配置操作 + public static IAppBuilder AddTenant( this IAppBuilder builder, Action setupAction ) { + builder.CheckNull( nameof( builder ) ); + setupAction ??= tenantOptions => tenantOptions.IsEnabled = true; + var options = new TenantOptions { + IsEnabled = true + }; + setupAction.Invoke( options ); + builder.Host.ConfigureServices( ( context, services ) => { + services.TryAddSingleton(); + services.Configure( setupAction ); + } ); + return builder; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/ITenantResolver.cs b/src/Util.Tenants/ITenantResolver.cs new file mode 100644 index 000000000..afa9b42a3 --- /dev/null +++ b/src/Util.Tenants/ITenantResolver.cs @@ -0,0 +1,16 @@ +namespace Util.Tenants; + +/// +/// 租户解析器 +/// +public interface ITenantResolver { + /// + /// 解析租户标识 + /// + /// Http上下文 + Task ResolveAsync( HttpContext context ); + /// + /// 优先级,值越大则优先解析 + /// + int Priority { get; set; } +} \ No newline at end of file diff --git a/src/Util.Tenants/Managements/SwitchTenantManager.cs b/src/Util.Tenants/Managements/SwitchTenantManager.cs new file mode 100644 index 000000000..2fb660057 --- /dev/null +++ b/src/Util.Tenants/Managements/SwitchTenantManager.cs @@ -0,0 +1,30 @@ +using Util.Helpers; + +namespace Util.Tenants.Managements; + +/// +/// 切换租户管理器 +/// +public class SwitchTenantManager : ISwitchTenantManager { + /// + /// 切换租户键名 + /// + public const string Key = "x-switch-tenant"; + + /// + public string GetSwitchTenantId() { + return Web.GetCookie( Key ); + } + + /// + public Task SwitchTenantAsync( string tenantId ) { + Web.SetCookie( Key, tenantId ); + return Task.CompletedTask; + } + + /// + public Task ResetTenantAsync() { + Web.RemoveCookie( Key ); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Managements/TenantManager.cs b/src/Util.Tenants/Managements/TenantManager.cs new file mode 100644 index 000000000..d787f6764 --- /dev/null +++ b/src/Util.Tenants/Managements/TenantManager.cs @@ -0,0 +1,105 @@ +namespace Util.Tenants.Managements; + +/// +/// 租户管理器 +/// +public class TenantManager : ITenantManager { + /// + /// 当前租户标识 + /// + private static readonly AsyncLocal _currentTenantId; + + /// + /// 初始化租户管理器 + /// + static TenantManager() { + _currentTenantId = new AsyncLocal(); + } + + /// + /// 初始化租户管理器 + /// + /// 租户存储器 + /// 查看租户管理器 + /// 切换租户管理器 + /// 用户会话 + /// 租户配置 + public TenantManager( ITenantStore tenantStore, IViewAllTenantManager viewAllTenantManager, ISwitchTenantManager switchTenantManager, + Sessions.ISession session, IOptions options ) { + TenantStore = tenantStore ?? throw new ArgumentNullException( nameof( tenantStore ) ); + ViewAllTenantManager = viewAllTenantManager ?? throw new ArgumentNullException( nameof( viewAllTenantManager ) ); + SwitchTenantManager = switchTenantManager ?? throw new ArgumentNullException( nameof( switchTenantManager ) ); + Session = session ?? throw new ArgumentNullException( nameof( session ) ); + Options = options?.Value ?? TenantOptions.Null; + } + + /// + /// 租户存储器 + /// + protected ITenantStore TenantStore { get; } + /// + /// 查看租户管理器 + /// + protected IViewAllTenantManager ViewAllTenantManager { get; } + /// + /// 切换租户管理器 + /// + protected ISwitchTenantManager SwitchTenantManager { get; } + /// + /// 用户会话 + /// + protected Sessions.ISession Session { get; } + /// + /// 租户配置 + /// + protected TenantOptions Options { get; } + + /// + /// 当前租户标识 + /// + public static string CurrentTenantId { + get => _currentTenantId.Value; + set => _currentTenantId.Value = value; + } + + /// + public virtual bool Enabled() { + return Options.IsEnabled; + } + + /// + public virtual bool AllowMultipleDatabase() { + return Options.IsAllowMultipleDatabase; + } + + /// + public virtual bool IsHost() { + if ( Session.IsAuthenticated == false ) + return false; + if ( Session.UserId.IsEmpty() ) + return false; + if ( Session.TenantId != CurrentTenantId ) + return false; + return Session.TenantId == Options.DefaultTenantId; + } + + /// + public virtual string GetTenantId() { + if ( IsHost() == false ) + return CurrentTenantId; + var switchTenantId = SwitchTenantManager.GetSwitchTenantId(); + return switchTenantId.IsEmpty() ? CurrentTenantId : switchTenantId; + } + + /// + public virtual TenantInfo GetTenant() { + return TenantStore.GetTenant( GetTenantId() ); + } + + /// + public virtual bool IsDisableTenantFilter() { + if ( IsHost() == false ) + return false; + return ViewAllTenantManager.IsDisableTenantFilter(); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Managements/ViewAllTenantManager.cs b/src/Util.Tenants/Managements/ViewAllTenantManager.cs new file mode 100644 index 000000000..ad382e662 --- /dev/null +++ b/src/Util.Tenants/Managements/ViewAllTenantManager.cs @@ -0,0 +1,33 @@ +using Util.Helpers; + +namespace Util.Tenants.Managements; + +/// +/// 查看租户管理器 +/// +public class ViewAllTenantManager : IViewAllTenantManager { + /// + /// 查看所有租户键名 + /// + public const string Key = "x-view-all-tenant"; + + /// + public bool IsDisableTenantFilter() { + var result = Web.GetCookie( Key ); + if ( result.IsEmpty() ) + return false; + return result.ToBool(); + } + + /// + public Task EnableViewAllAsync() { + Web.SetCookie( Key,"true" ); + return Task.CompletedTask; + } + + /// + public Task DisableViewAllAsync() { + Web.RemoveCookie( Key ); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Middlewares/TenantMiddleware.cs b/src/Util.Tenants/Middlewares/TenantMiddleware.cs new file mode 100644 index 000000000..6cf3e0525 --- /dev/null +++ b/src/Util.Tenants/Middlewares/TenantMiddleware.cs @@ -0,0 +1,45 @@ +using Util.Tenants.Managements; + +namespace Util.Tenants.Middlewares; + +/// +/// 租户处理中间件 +/// +public class TenantMiddleware { + /// + /// 中间件管道 + /// + private readonly RequestDelegate _next; + + /// + /// 初始化租户处理中间件 + /// + /// 中间件管道 + public TenantMiddleware( RequestDelegate next ) { + _next = next; + } + + /// + /// 执行管道 + /// + /// Http上下文 + public async Task InvokeAsync( HttpContext httpContext ) { + if ( _next == null ) + return; + if ( httpContext == null ) { + await _next( httpContext ); + return; + } + var log = httpContext.RequestServices.GetService>() ?? NullLogger.Instance; + var resolver = httpContext.RequestServices.GetRequiredService(); + try { + var tenantId = await resolver.ResolveAsync( httpContext ); + TenantManager.CurrentTenantId = tenantId; + await _next( httpContext ); + } + catch ( Exception exception ) { + log.LogError( exception, "租户处理中间件发生异常" ); + throw; + } + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/CookieTenantResolver.cs b/src/Util.Tenants/Resolvers/CookieTenantResolver.cs new file mode 100644 index 000000000..532abe9a2 --- /dev/null +++ b/src/Util.Tenants/Resolvers/CookieTenantResolver.cs @@ -0,0 +1,16 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 基于Cookie的租户解析器 +/// +public class CookieTenantResolver : TenantResolverBase { + /// + /// 解析租户标识 + /// + protected override Task Resolve( HttpContext context ) { + var key = GetTenantKey( context ); + var tenantId = context.Request.Cookies[key]; + GetLog( context ).LogTrace( $"执行Cookie租户解析器,{key}={tenantId}" ); + return Task.FromResult( tenantId ); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs b/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs new file mode 100644 index 000000000..5a25c96f7 --- /dev/null +++ b/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs @@ -0,0 +1,36 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 默认租户解析器 +/// +public class DefaultTenantResolver : ITenantResolver { + /// + /// 租户配置 + /// + private readonly TenantOptions _options; + /// + public int Priority { get; set; } + + /// + /// 初始化默认租户解析器 + /// + /// 租户配置 + public DefaultTenantResolver( IOptions options ) { + _options = options?.Value ?? TenantOptions.Null; + } + + /// + public async Task ResolveAsync( HttpContext context ) { + if ( _options.IsEnabled == false ) + return null; + var session = context.RequestServices.GetService(); + if ( session is { IsAuthenticated: true } ) + return session.TenantId; + foreach ( var resolver in _options.Resolvers.OrderByDescending( t => t.Priority ) ) { + var result = await resolver.ResolveAsync( context ); + if ( result.IsEmpty() == false ) + return result; + } + return null; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/DomainTenantResolver.cs b/src/Util.Tenants/Resolvers/DomainTenantResolver.cs new file mode 100644 index 000000000..fd8ef74f3 --- /dev/null +++ b/src/Util.Tenants/Resolvers/DomainTenantResolver.cs @@ -0,0 +1,115 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 基于域名的租户解析器 +/// +public class DomainTenantResolver : TenantResolverBase { + /// + /// 域名格式字符串 + /// + private readonly string _domainFormat; + /// + /// 域名映射配置 + /// + private readonly IDictionary _domainMap; + + /// + /// 初始化基于域名的租户解析器 + /// + public DomainTenantResolver() { + } + + /// + /// 初始化基于域名的租户解析器 + /// + /// 域名格式字符串,范例:{0}.a.com + public DomainTenantResolver( string domainFormat ) { + _domainFormat = domainFormat; + } + + /// + /// 初始化基于域名的租户解析器 + /// + /// 域名映射配置,键为域名,值为租户标识 + public DomainTenantResolver( IDictionary domainMap ) { + _domainMap = domainMap; + } + + /// + /// 解析租户标识 + /// + protected override async Task Resolve( HttpContext context ) { + var host = GetDomain( context.Request.Host.Value ); + if ( host.IsEmpty() ) + return null; + var result = await Resolve( context,host ); + GetLog( context ).LogTrace( $"执行域名租户解析器,host={host},tenantId={result}" ); + return result; + } + + /// + /// 获取域名 + /// + private string GetDomain( string domain ) { + return domain.RemoveStart( "http://" ).RemoveStart( "https://" ); + } + + /// + /// 解析租户标识 + /// + private async Task Resolve( HttpContext context, string host ) { + if ( _domainMap != null ) + return ResolveByMap( host ); + if ( _domainFormat.IsEmpty() == false ) + return ResolveByFormat( host ); + var resolver = context.RequestServices.GetService(); + return resolver == null ? ResolveBySplit( host ) : await resolver.ResolveTenantIdAsync( host ); + } + + /// + /// 通过域名映射配置解析 + /// + private string ResolveByMap( string host ) { + foreach ( var item in _domainMap ) { + if ( item.Key == host ) + return item.Value; + } + return null; + } + + /// + /// 通过域名格式字符串解析 + /// + private string ResolveByFormat( string host ) { + var formats = GetDomainFormats(); + foreach ( var format in formats ) { + if ( format.IsEmpty() ) + continue; + var result = Util.Helpers.String.Extract( host, GetDomain( format ) ).FirstOrDefault().Value; + if( result.IsEmpty() == false ) + return result; + } + return null; + } + + /// + /// 获取域名格式列表 + /// + private IEnumerable GetDomainFormats() { + if ( _domainFormat.IndexOf( ",", StringComparison.Ordinal ) >= 0 ) + return _domainFormat.Split( ',' ); + if ( _domainFormat.IndexOf( ";", StringComparison.Ordinal ) >= 0 ) + return _domainFormat.Split( ';' ); + return new[] { _domainFormat }; + } + + /// + /// 通过拆分解析 + /// + private string ResolveBySplit( string host ) { + var items = host.Split( '.' ); + if ( items.Length > 2 ) + return items[0]; + return null; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs b/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs new file mode 100644 index 000000000..a5cfc95d8 --- /dev/null +++ b/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs @@ -0,0 +1,17 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 基于请求头的租户解析器 +/// +public class HeaderTenantResolver : TenantResolverBase { + /// + /// 解析租户标识 + /// + protected override Task Resolve( HttpContext context ) { + var key = GetTenantKey( context ); + context.Request.Headers.TryGetValue( key, out var result ); + var tenantId = result.FirstOrDefault(); + GetLog( context ).LogTrace( $"执行请求头租户解析器,{key}={tenantId}" ); + return Task.FromResult( tenantId ); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs b/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs new file mode 100644 index 000000000..47c11a064 --- /dev/null +++ b/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs @@ -0,0 +1,14 @@ +using Util.Dependency; + +namespace Util.Tenants.Resolvers; + +/// +/// 基于域名的租户解析器 +/// +public interface IDomainTenantResolver : ITransientDependency { + /// + /// 解析租户标识 + /// + /// 域名,范例: a.test.com + Task ResolveTenantIdAsync( string host ); +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs b/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs new file mode 100644 index 000000000..d07cf200c --- /dev/null +++ b/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs @@ -0,0 +1,16 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 基于查询字符串的租户解析器 +/// +public class QueryStringTenantResolver : TenantResolverBase { + /// + /// 解析租户标识 + /// + protected override Task Resolve( HttpContext context ) { + var key = GetTenantKey( context ); + var tenantId = context.Request.Query[key].FirstOrDefault(); + GetLog( context ).LogTrace( $"执行查询字符串租户解析器,{key}={tenantId}" ); + return Task.FromResult( tenantId ); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Resolvers/TenantResolverBase.cs b/src/Util.Tenants/Resolvers/TenantResolverBase.cs new file mode 100644 index 000000000..be0e03ee6 --- /dev/null +++ b/src/Util.Tenants/Resolvers/TenantResolverBase.cs @@ -0,0 +1,36 @@ +namespace Util.Tenants.Resolvers; + +/// +/// 租户解析器 +/// +public abstract class TenantResolverBase : ITenantResolver { + /// + public int Priority { get; set; } + + /// + public async Task ResolveAsync( HttpContext context ) { + if ( context == null ) + return null; + return await Resolve( context ); + } + + /// + /// 解析租户标识 + /// + protected abstract Task Resolve( HttpContext context ); + + /// + /// 获取租户键名 + /// + protected string GetTenantKey( HttpContext context ) { + var options = context.RequestServices.GetRequiredService>(); + return options.Value.TenantKey; + } + + /// + /// 获取日志记录器 + /// + protected ILogger GetLog( HttpContext context ) { + return context.RequestServices.GetService>() ?? NullLogger.Instance; + } +} \ No newline at end of file diff --git a/src/Util.Tenants/TenantMiddlewareExtensions.cs b/src/Util.Tenants/TenantMiddlewareExtensions.cs new file mode 100644 index 000000000..24cafa643 --- /dev/null +++ b/src/Util.Tenants/TenantMiddlewareExtensions.cs @@ -0,0 +1,16 @@ +using Util.Tenants.Middlewares; + +namespace Util.Tenants; + +/// +/// 租户处理中间件扩展 +/// +public static class TenantMiddlewareExtensions { + /// + /// 注册租户处理中间件,注意: 必须在 app.UseAuthentication() 之后调用 + /// + /// 应用程序生成器 + public static IApplicationBuilder UseTenant( this IApplicationBuilder builder ) { + return builder.UseMiddleware(); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/TenantOptions.cs b/src/Util.Tenants/TenantOptions.cs new file mode 100644 index 000000000..9f365b178 --- /dev/null +++ b/src/Util.Tenants/TenantOptions.cs @@ -0,0 +1,56 @@ +using Util.Tenants.Resolvers; + +namespace Util.Tenants; + +/// +/// 租户配置 +/// +public class TenantOptions { + /// + /// 初始化租户配置 + /// + public TenantOptions() { + TenantKey = DefaultTenantKey; + Resolvers = new TenantResolverCollection { + new HeaderTenantResolver{ Priority = 9 }, + new QueryStringTenantResolver{ Priority = 7 }, + new CookieTenantResolver{ Priority = 5 }, + new DomainTenantResolver{ Priority = 3 } + }; + } + + /// + /// 租户配置空实例 + /// + public static readonly TenantOptions Null = new(); + + /// + /// 默认租户键名,默认值: x-tenant-id + /// + public const string DefaultTenantKey = "x-tenant-id"; + + /// + /// 是否启用多租户架构 + /// + public bool IsEnabled { get; set; } + + /// + /// 是否允许使用多数据库 + /// + public bool IsAllowMultipleDatabase { get; set; } + + /// + /// 默认租户标识 + /// + public string DefaultTenantId { get; set; } + + /// + /// 租户键名,默认值: x-tenant-id + /// + public string TenantKey { get; set; } + + /// + /// 租户解析器集合 + /// + public TenantResolverCollection Resolvers { get; } +} \ No newline at end of file diff --git a/src/Util.Tenants/TenantResolverCollection.cs b/src/Util.Tenants/TenantResolverCollection.cs new file mode 100644 index 000000000..7eb08d349 --- /dev/null +++ b/src/Util.Tenants/TenantResolverCollection.cs @@ -0,0 +1,99 @@ +namespace Util.Tenants; + +/// +/// 租户解析器集合 +/// +public class TenantResolverCollection : IEnumerable { + /// + /// 租户解析器列表 + /// + private readonly Dictionary _resolvers; + + /// + /// 初始化租户解析器集合 + /// + public TenantResolverCollection() { + _resolvers = new Dictionary(); + } + + /// + /// 获取枚举器 + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + /// + /// 获取枚举器 + /// + public IEnumerator GetEnumerator() { + return GetResolvers().GetEnumerator(); + } + + /// + /// 获取租户解析器列表 + /// + public List GetResolvers() { + return _resolvers.Select( t => t.Value ).ToList(); + } + + /// + /// 添加租户解析器 + /// + /// 租户解析器 + public void Add( ITenantResolver resolver ) { + var key = resolver.GetType().FullName; + Add( key, resolver ); + } + + /// + /// 添加租户解析器 + /// + /// 租户解析器键名 + /// 租户解析器 + public void Add( string key, ITenantResolver resolver ) { + if ( key.IsEmpty() ) + return; + if ( resolver == null ) + return; + Remove( key ); + _resolvers.Add( key, resolver ); + } + + /// + /// 添加租户解析器列表 + /// + /// 租户解析器列表 + public void Add( IList resolvers ) { + if ( resolvers == null ) + return; + foreach ( var resolver in resolvers ) + Add( resolver ); + } + + /// + /// 移除租户解析器 + /// + /// 租户解析器类型 + public void Remove() where TResolver : ITenantResolver { + var key = typeof( TResolver ).FullName; + Remove( key ); + } + + /// + /// 移除租户解析器 + /// + /// 租户解析器键名 + public void Remove( string key ) { + if ( key.IsEmpty() ) + return; + _resolvers.Remove( key ); + } + + /// + /// 清空租户解析器 + /// + public void Clear() { + _resolvers.Clear(); + } +} \ No newline at end of file diff --git a/src/Util.Tenants/Usings.cs b/src/Util.Tenants/Usings.cs new file mode 100644 index 000000000..6782ec2b8 --- /dev/null +++ b/src/Util.Tenants/Usings.cs @@ -0,0 +1,13 @@ +global using System; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.Linq; +global using System.Collections; +global using System.Threading; +global using Microsoft.AspNetCore.Http; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Logging.Abstractions; +global using Microsoft.AspNetCore.Builder; \ No newline at end of file diff --git a/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj b/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj index dbc12eb27..b5b46b090 100644 --- a/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj +++ b/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj index db13bd332..9adc9e169 100644 --- a/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj +++ b/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj index 490dd1a58..5b1dbf784 100644 --- a/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj +++ b/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj index 5e28e710b..ef7ac05ec 100644 --- a/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj +++ b/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.Tests/Util.Application.Tests.csproj b/test/Util.Application.Tests/Util.Application.Tests.csproj index 6f7a7b54f..5b4400b99 100644 --- a/test/Util.Application.Tests/Util.Application.Tests.csproj +++ b/test/Util.Application.Tests/Util.Application.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj b/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj index 8abf5b389..830a93103 100644 --- a/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj +++ b/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj @@ -9,11 +9,11 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj b/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj index dd79af117..9c27966b2 100644 --- a/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj +++ b/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj @@ -9,11 +9,11 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj b/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj index ab20e1a20..57d96acd1 100644 --- a/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj +++ b/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj @@ -9,11 +9,11 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj b/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj index 58aacec8e..c5b78a775 100644 --- a/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj +++ b/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj @@ -10,10 +10,10 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs b/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs index c9c8c5497..5be603835 100644 --- a/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs +++ b/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs @@ -55,4 +55,12 @@ public ActionResult> List( [FromQuery] CustomerQuery query ) { }; return result; } + + /// + /// 获取cookie值 + /// + [HttpGet( "cookie" )] + public string GetCookie() { + return $"code:{Request.Cookies["code"]},name:{Request.Cookies["name"]}"; + } } \ No newline at end of file diff --git a/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs b/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs index f2163b5bd..2f220bccb 100644 --- a/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs +++ b/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs @@ -145,4 +145,26 @@ public async Task TestGet_QueryString_6() { } #endregion + + #region Cookie + + /// + /// ԵGet - cookie - 1 + /// + [Fact] + public async Task TestGet_Cookie_1() { + var result = await _client.Get( "/api/test1/cookie" ).Cookie( "code", "a" ).GetResultAsync(); + Assert.Equal( "code:a,name:", result ); + } + + /// + /// ԵGet - cookie - 2 + /// + [Fact] + public async Task TestGet_Cookie_2() { + var result = await _client.Get( "/api/test1/cookie" ).Cookie( "code", "a" ).Cookie( "name", "b" ).GetResultAsync(); + Assert.Equal( "code:a,name:b", result ); + } + + #endregion } \ No newline at end of file diff --git a/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj b/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj index bbed9c24a..224726ac0 100644 --- a/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj +++ b/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj @@ -10,9 +10,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj b/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj index 7cbdcf51d..4b6d7b9c4 100644 --- a/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj +++ b/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj @@ -9,9 +9,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Core.Tests.Integration/Helpers/FileTest.cs b/test/Util.Core.Tests.Integration/Helpers/FileTest.cs index 06bee88f1..7733584eb 100644 --- a/test/Util.Core.Tests.Integration/Helpers/FileTest.cs +++ b/test/Util.Core.Tests.Integration/Helpers/FileTest.cs @@ -1,28 +1,28 @@ -using System.Threading.Tasks; -using Util.Helpers; -using Xunit; - -namespace Util.Tests.Helpers; - -/// -/// 文件流操作 -/// -public class FileTest { - /// - /// 测试读取文件到字符串 - /// - [Fact] - public void TestReadToString() { - var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" ); - Assert.Equal( "test", File.ReadToString( filePath ) ); - } - - /// - /// 测试读取文件到字符串 - /// - [Fact] - public async Task TestReadToStringAsync() { - var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" ); - Assert.Equal( "test",await File.ReadToStringAsync( filePath ) ); - } +using System.Threading.Tasks; +using Util.Helpers; +using Xunit; + +namespace Util.Tests.Helpers; + +/// +/// 文件流操作 +/// +public class FileTest { + /// + /// 测试读取文件到字符串 + /// + [Fact] + public void TestReadToString() { + var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" ); + Assert.Equal( "test", File.ReadToString( filePath ) ); + } + + /// + /// 测试读取文件到字符串 + /// + [Fact] + public async Task TestReadToStringAsync() { + var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" ); + Assert.Equal( "test",await File.ReadToStringAsync( filePath ) ); + } } \ No newline at end of file diff --git a/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj b/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj index 1d08f0f96..1f84da100 100644 --- a/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj +++ b/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj @@ -20,7 +20,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Core.Tests/Helpers/StringTest.cs b/test/Util.Core.Tests/Helpers/StringTest.cs index f8b1c7b9b..0a50da165 100644 --- a/test/Util.Core.Tests/Helpers/StringTest.cs +++ b/test/Util.Core.Tests/Helpers/StringTest.cs @@ -2,35 +2,43 @@ using System.Collections.Generic; using System.Text; using Xunit; +using String = Util.Helpers.String; -namespace Util.Tests.Helpers; +namespace Util.Tests.Helpers; /// /// 测试字符串操作 - 工具库 /// public class StringTest { + + #region Join + /// /// 测试将集合连接为带分隔符的字符串 /// [Fact] public void TestJoin() { - Assert.Equal( "1,2,3", Util.Helpers.String.Join( new List { 1, 2, 3 } ) ); - Assert.Equal( "'1','2','3'", Util.Helpers.String.Join( new List { 1, 2, 3 }, "'" ) ); - Assert.Equal( "123", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", "" ) ); - Assert.Equal( "\"1\",\"2\",\"3\"", Util.Helpers.String.Join( new List { 1, 2, 3 }, "\"" ) ); - Assert.Equal( "1 2 3", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", " " ) ); - Assert.Equal( "1;2;3", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", ";" ) ); - Assert.Equal( "1,2,3", Util.Helpers.String.Join( new List { "1", "2", "3" } ) ); - Assert.Equal( "'1','2','3'", Util.Helpers.String.Join( new List { "1", "2", "3" }, "'" ) ); + Assert.Equal( "1,2,3", String.Join( new List { 1, 2, 3 } ) ); + Assert.Equal( "'1','2','3'", String.Join( new List { 1, 2, 3 }, "'" ) ); + Assert.Equal( "123", String.Join( new List { 1, 2, 3 }, "", "" ) ); + Assert.Equal( "\"1\",\"2\",\"3\"", String.Join( new List { 1, 2, 3 }, "\"" ) ); + Assert.Equal( "1 2 3", String.Join( new List { 1, 2, 3 }, "", " " ) ); + Assert.Equal( "1;2;3", String.Join( new List { 1, 2, 3 }, "", ";" ) ); + Assert.Equal( "1,2,3", String.Join( new List { "1", "2", "3" } ) ); + Assert.Equal( "'1','2','3'", String.Join( new List { "1", "2", "3" }, "'" ) ); var list = new List { new( "83B0233C-A24F-49FD-8083-1337209EBC9A" ), new( "EAB523C6-2FE7-47BE-89D5-C6D440C3033A" ) }; - Assert.Equal( "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A".ToLower(), Util.Helpers.String.Join( list ) ); - Assert.Equal( "'83B0233C-A24F-49FD-8083-1337209EBC9A','EAB523C6-2FE7-47BE-89D5-C6D440C3033A'".ToLower(), Util.Helpers.String.Join( list, "'" ) ); + Assert.Equal( "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A".ToLower(), String.Join( list ) ); + Assert.Equal( "'83B0233C-A24F-49FD-8083-1337209EBC9A','EAB523C6-2FE7-47BE-89D5-C6D440C3033A'".ToLower(), String.Join( list, "'" ) ); } + #endregion + + #region FirstLowerCase + /// /// 首字母小写 /// @@ -44,9 +52,13 @@ public void TestJoin() { [InlineData( "AB", "aB" )] [InlineData( "Abc", "abc" )] public void TestFirstLowerCase( string value, string result ) { - Assert.Equal( result, Util.Helpers.String.FirstLowerCase( value ) ); + Assert.Equal( result, String.FirstLowerCase( value ) ); } + #endregion + + #region FirstUpperCase + /// /// 首字母大写 /// @@ -60,9 +72,13 @@ public void TestFirstLowerCase( string value, string result ) { [InlineData( "aB", "AB" )] [InlineData( "abc", "Abc" )] public void TestFirstUpperCase( string value, string result ) { - Assert.Equal( result, Util.Helpers.String.FirstUpperCase( value ) ); + Assert.Equal( result, String.FirstUpperCase( value ) ); } + #endregion + + #region RemoveStart + /// /// 测试移除起始字符串 /// @@ -80,26 +96,7 @@ public void TestFirstUpperCase( string value, string result ) { [InlineData( "a.cs.cshtml", "a.cs", ".cshtml" )] [InlineData( "\r\na", "\r\n", "a" )] public void TestRemoveStart( string value, string removeValue, string result ) { - Assert.Equal( result, Util.Helpers.String.RemoveStart( value, removeValue ) ); - } - - /// - /// 测试移除末尾字符串 - /// - [Theory] - [InlineData( null, null,"" )] - [InlineData( null, "a", "" )] - [InlineData( "", "","" )] - [InlineData( "a", "b", "a" )] - [InlineData( "ab", "a", "ab" )] - [InlineData( "ab", "b", "a" )] - [InlineData( "abc", "abc", "" )] - [InlineData( "bc", "abc", "bc" )] - [InlineData( "ab", "abc", "ab" )] - [InlineData( "a.cs.cshtml", ".cshtml", "a.cs" )] - [InlineData( "a\r\n", "\r\n", "a" )] - public void TestRemoveEnd( string value,string removeValue,string result ) { - Assert.Equal( result, Util.Helpers.String.RemoveEnd( value, removeValue ) ); + Assert.Equal( result, String.RemoveStart( value, removeValue ) ); } /// @@ -133,6 +130,29 @@ public void TestRemoveStart_StringBuilder_2( string value, string removeValue, s Assert.Equal( result, Util.Helpers.String.RemoveStart( builder, removeValue )?.ToString() ); } + #endregion + + #region RemoveEnd + + /// + /// 测试移除末尾字符串 + /// + [Theory] + [InlineData( null, null, "" )] + [InlineData( null, "a", "" )] + [InlineData( "", "", "" )] + [InlineData( "a", "b", "a" )] + [InlineData( "ab", "a", "ab" )] + [InlineData( "ab", "b", "a" )] + [InlineData( "abc", "abc", "" )] + [InlineData( "bc", "abc", "bc" )] + [InlineData( "ab", "abc", "ab" )] + [InlineData( "a.cs.cshtml", ".cshtml", "a.cs" )] + [InlineData( "a\r\n", "\r\n", "a" )] + public void TestRemoveEnd( string value, string removeValue, string result ) { + Assert.Equal( result, String.RemoveEnd( value, removeValue ) ); + } + /// /// 测试移除末尾字符串 /// @@ -163,6 +183,10 @@ public void TestRemoveEnd_StringBuilder_2( string value, string removeValue, str Assert.Equal( result, Util.Helpers.String.RemoveEnd( builder, removeValue )?.ToString() ); } + #endregion + + #region PinYin + /// /// 测试获取拼音简码 /// @@ -174,6 +198,165 @@ public void TestRemoveEnd_StringBuilder_2( string value, string removeValue, str [InlineData( "饕餮", "tt" )] [InlineData( "爩", "y" )] public void TestPinYin( string input, string result ) { - Assert.Equal( result, Util.Helpers.String.PinYin( input ) ); + Assert.Equal( result, String.PinYin( input ) ); + } + + #endregion + + #region Extract + + /// + /// 测试提取字符串中的变量值 - 原始值为空 + /// + [Fact] + public void TestExtract_1() { + var result = String.Extract( "", "a" ); + Assert.Empty( result ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串为空 + /// + [Fact] + public void TestExtract_2() { + var result = String.Extract( "a", "" ); + Assert.Empty( result ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串未包含{} + /// + [Fact] + public void TestExtract_3() { + var result = String.Extract( "a", "a" ); + Assert.Empty( result ); } + + /// + /// 测试提取字符串中的变量值 - 格式字符串未包含{} + /// + [Fact] + public void TestExtract_4() { + var result = String.Extract( "a", "{value}" ); + Assert.Single( result ); + Assert.Equal( "a", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 值前后有空格 + /// + [Fact] + public void TestExtract_5() { + var result = String.Extract( " a ", "{value}" ); + Assert.Single( result ); + Assert.Equal( "a", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串前后有空格 + /// + [Fact] + public void TestExtract_6() { + var result = String.Extract( "a", " {value} " ); + Assert.Single( result ); + Assert.Equal( "a", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串左侧有文本 - 忽略大小写 + /// + [Fact] + public void TestExtract_7() { + var result = String.Extract( "Hello,World", "hello,{value}" ); + Assert.Single( result ); + Assert.Equal( "World", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串右侧有文本 - 忽略大小写 + /// + [Fact] + public void TestExtract_8() { + var result = String.Extract( "Hello,World", "{value},world" ); + Assert.Single( result ); + Assert.Equal( "Hello", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串左右侧都有文本 + /// + [Fact] + public void TestExtract_9() { + var result = String.Extract( "Hello,Util,World", "Hello,{value},world" ); + Assert.Single( result ); + Assert.Equal( "Util", result["value"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串包含两个变量 - 左侧文本 + /// + [Fact] + public void TestExtract_10() { + var result = String.Extract( "Hello,Util,World", "Hello,{a},{b}" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "Util", result["a"] ); + Assert.Equal( "World", result["b"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串包含两个变量 - 右侧文本 + /// + [Fact] + public void TestExtract_11() { + var result = String.Extract( "Hello,Util,World", "{a},{b},World" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "Hello", result["a"] ); + Assert.Equal( "Util", result["b"] ); + } + + /// + /// 测试提取字符串中的变量值 - 格式字符串包含两个变量 - 重复文本 + /// + [Fact] + public void TestExtract_12() { + var result = String.Extract( "Hello,Util,Hello,Test,Hello,World", "Hello,{a},Hello,{b},Hello,World" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "Util", result["a"] ); + Assert.Equal( "Test", result["b"] ); + } + + /// + /// 测试提取字符串中的变量值 - 没有分隔符 + /// + [Fact] + public void TestExtract_13() { + var result = String.Extract( "acababcabcd", "a{b}c{d}" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "cabab", result["b"] ); + Assert.Equal( "abcd", result["d"] ); + } + + /// + /// 测试提取字符串中的变量值 - 未匹配到变量b + /// + [Fact] + public void TestExtract_14() { + var result = String.Extract( "Hello,Util,World", "Hello,{a},{b},World" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "Util", result["a"] ); + Assert.Equal( "", result["b"] ); + } + + /// + /// 测试提取字符串中的变量值 - 多个括号 + /// + [Fact] + public void TestExtract_15() { + var result = String.Extract( "Hello,Util,Hello,Test,Hello,World", "Hello,{{a}},Hello,{{{b}}},Hello,World" ); + Assert.Equal( 2, result.Count ); + Assert.Equal( "Util", result["a"] ); + Assert.Equal( "Test", result["b"] ); + } + + #endregion } \ No newline at end of file diff --git a/test/Util.Core.Tests/Util.Core.Tests.csproj b/test/Util.Core.Tests/Util.Core.Tests.csproj index 19123b787..a2bd18dbb 100644 --- a/test/Util.Core.Tests/Util.Core.Tests.csproj +++ b/test/Util.Core.Tests/Util.Core.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/Util.Data.Dapper.MySql.Tests.Integration/Util.Data.Dapper.MySql.Tests.Integration.csproj b/test/Util.Data.Dapper.MySql.Tests.Integration/Util.Data.Dapper.MySql.Tests.Integration.csproj index dbd41c59b..969b5fd87 100644 --- a/test/Util.Data.Dapper.MySql.Tests.Integration/Util.Data.Dapper.MySql.Tests.Integration.csproj +++ b/test/Util.Data.Dapper.MySql.Tests.Integration/Util.Data.Dapper.MySql.Tests.Integration.csproj @@ -17,13 +17,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.Dapper.PostgreSql.Tests.Integration/Util.Data.Dapper.PostgreSql.Tests.Integration.csproj b/test/Util.Data.Dapper.PostgreSql.Tests.Integration/Util.Data.Dapper.PostgreSql.Tests.Integration.csproj index 82b81becd..10ab1ad7b 100644 --- a/test/Util.Data.Dapper.PostgreSql.Tests.Integration/Util.Data.Dapper.PostgreSql.Tests.Integration.csproj +++ b/test/Util.Data.Dapper.PostgreSql.Tests.Integration/Util.Data.Dapper.PostgreSql.Tests.Integration.csproj @@ -17,13 +17,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.Dapper.SqlServer.Tests.Integration/Util.Data.Dapper.SqlServer.Tests.Integration.csproj b/test/Util.Data.Dapper.SqlServer.Tests.Integration/Util.Data.Dapper.SqlServer.Tests.Integration.csproj index 70d70ab2c..f8f6c9488 100644 --- a/test/Util.Data.Dapper.SqlServer.Tests.Integration/Util.Data.Dapper.SqlServer.Tests.Integration.csproj +++ b/test/Util.Data.Dapper.SqlServer.Tests.Integration/Util.Data.Dapper.SqlServer.Tests.Integration.csproj @@ -17,13 +17,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/DeleteFilterTest.cs b/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/DeleteFilterTest.cs deleted file mode 100644 index 0828d6abf..000000000 --- a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/DeleteFilterTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Util.Tests.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Util.Data.EntityFrameworkCore.Filters; - -/// -/// 逻辑删除过滤器测试 -/// -public class DeleteFilterTest { - /// - /// 测试消息输出 - /// - private readonly ITestOutputHelper _testOutputHelper; - /// - /// 逻辑删除过滤器 - /// - private readonly DeleteFilter _filter; - - /// - /// 测试初始化 - /// - public DeleteFilterTest( ITestOutputHelper testOutputHelper ) { - _testOutputHelper = testOutputHelper; - _filter = new DeleteFilter(); - } - - /// - /// 测试是否启用 - /// - [Fact] - public void TestIsEnabled() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - _filter.Disable(); - Assert.False( _filter.IsEnabled ); - - //启用过滤器 - _filter.Enable(); - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试禁用 - /// - [Fact] - public void TestDisable() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - using( _filter.Disable() ) { - Assert.False( _filter.IsEnabled ); - } - - //恢复启用状态 - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filter.IsEntityEnabled() ); - Assert.True( _filter.IsEntityEnabled() ); - } - - /// - /// 测试获取表达式 - /// - [Fact] - public void TestGetExpression() { - var expression = _filter.GetExpression(); - var result = "entity => Not(Property(entity, \"IsDeleted\"))"; - _testOutputHelper.WriteLine( expression.ToString() ); - Assert.Equal( result,expression.ToString() ); - } -} \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/FilterManagerTest.cs b/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/FilterManagerTest.cs deleted file mode 100644 index 66c5e54be..000000000 --- a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/FilterManagerTest.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Util.Data.Filters; -using Util.Domain; -using Util.Tests.Models; -using Xunit; - -namespace Util.Data.EntityFrameworkCore.Filters; - -/// -/// 数据过滤器管理器测试 -/// -public class FilterManagerTest { - /// - /// 数据过滤器管理器 - /// - private readonly IFilterManager _filterManager; - - /// - /// 测试初始化 - /// - public FilterManagerTest( IFilterManager filterManager ) { - _filterManager = filterManager; - } - - /// - /// 测试获取过滤器 - 泛型 - /// - [Fact] - public void TestGetFilter_1() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(); - Assert.NotNull( filter ); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试获取过滤器 - 非泛型 - /// - [Fact] - public void TestGetFilter_2() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(typeof( IDelete ) ); - Assert.NotNull( filter ); - } - - /// - /// 测试禁用过滤器 - /// - [Fact] - public void TestDisableFilter_1() { - _filterManager.DisableFilter(); - var filter = _filterManager.GetFilter(); - Assert.False( filter.IsEnabled ); - } - - /// - /// 测试禁用过滤器 - 使用using - /// - [Fact] - public void TestDisableFilter_2() { - var filter = _filterManager.GetFilter(); - using ( _filterManager.DisableFilter() ) { - Assert.False( filter.IsEnabled ); - } - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试启用过滤器 - /// - [Fact] - public void TestEnableFilter() { - var filter = _filterManager.GetFilter(); - - //禁用 - _filterManager.DisableFilter(); - Assert.False( filter.IsEnabled ); - - //启用 - _filterManager.EnableFilter(); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filterManager.IsEntityEnabled() ); - Assert.True( _filterManager.IsEntityEnabled() ); - } - - /// - /// 测试过滤器是否启用 - /// - [Fact] - public void TestIsEnabled() { - Assert.True( _filterManager.IsEnabled() ); - Assert.False( _filterManager.IsEnabled() ); - } -} \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Util.Data.EntityFrameworkCore.MySql.Tests.Integration.csproj b/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Util.Data.EntityFrameworkCore.MySql.Tests.Integration.csproj index e60c2ee7d..3fffc8faa 100644 --- a/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Util.Data.EntityFrameworkCore.MySql.Tests.Integration.csproj +++ b/test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Util.Data.EntityFrameworkCore.MySql.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/DeleteFilterTest.cs b/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/DeleteFilterTest.cs deleted file mode 100644 index bfba9778a..000000000 --- a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/DeleteFilterTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Util.Tests.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 逻辑删除过滤器测试 - /// - public class DeleteFilterTest { - /// - /// 测试消息输出 - /// - private readonly ITestOutputHelper _testOutputHelper; - /// - /// 逻辑删除过滤器 - /// - private readonly DeleteFilter _filter; - - /// - /// 测试初始化 - /// - public DeleteFilterTest( ITestOutputHelper testOutputHelper ) { - _testOutputHelper = testOutputHelper; - _filter = new DeleteFilter(); - } - - /// - /// 测试是否启用 - /// - [Fact] - public void TestIsEnabled() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - _filter.Disable(); - Assert.False( _filter.IsEnabled ); - - //启用过滤器 - _filter.Enable(); - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试禁用 - /// - [Fact] - public void TestDisable() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - using( _filter.Disable() ) { - Assert.False( _filter.IsEnabled ); - } - - //恢复启用状态 - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filter.IsEntityEnabled() ); - Assert.True( _filter.IsEntityEnabled() ); - } - - /// - /// 测试获取表达式 - /// - [Fact] - public void TestGetExpression() { - var expression = _filter.GetExpression(); - var result = "entity => Not(Property(entity, \"IsDeleted\"))"; - _testOutputHelper.WriteLine( expression.ToString() ); - Assert.Equal( result,expression.ToString() ); - } - } -} diff --git a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/FilterManagerTest.cs b/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/FilterManagerTest.cs deleted file mode 100644 index 62514068c..000000000 --- a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/FilterManagerTest.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Util.Data.Filters; -using Util.Domain; -using Util.Tests.Models; -using Xunit; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 数据过滤器管理器测试 - /// - public class FilterManagerTest { - /// - /// 数据过滤器管理器 - /// - private readonly IFilterManager _filterManager; - - /// - /// 测试初始化 - /// - public FilterManagerTest( IFilterManager filterManager ) { - _filterManager = filterManager; - } - - /// - /// 测试获取过滤器 - 泛型 - /// - [Fact] - public void TestGetFilter_1() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(); - Assert.NotNull( filter ); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试获取过滤器 - 非泛型 - /// - [Fact] - public void TestGetFilter_2() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(typeof( IDelete ) ); - Assert.NotNull( filter ); - } - - /// - /// 测试禁用过滤器 - /// - [Fact] - public void TestDisableFilter_1() { - _filterManager.DisableFilter(); - var filter = _filterManager.GetFilter(); - Assert.False( filter.IsEnabled ); - } - - /// - /// 测试禁用过滤器 - 使用using - /// - [Fact] - public void TestDisableFilter_2() { - var filter = _filterManager.GetFilter(); - using ( _filterManager.DisableFilter() ) { - Assert.False( filter.IsEnabled ); - } - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试启用过滤器 - /// - [Fact] - public void TestEnableFilter() { - var filter = _filterManager.GetFilter(); - - //禁用 - _filterManager.DisableFilter(); - Assert.False( filter.IsEnabled ); - - //启用 - _filterManager.EnableFilter(); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filterManager.IsEntityEnabled() ); - Assert.True( _filterManager.IsEntityEnabled() ); - } - - /// - /// 测试过滤器是否启用 - /// - [Fact] - public void TestIsEnabled() { - Assert.True( _filterManager.IsEnabled() ); - Assert.False( _filterManager.IsEnabled() ); - } - } -} - diff --git a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration.csproj b/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration.csproj index 900e2b2ec..083f0ed6c 100644 --- a/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration.csproj +++ b/test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration.csproj @@ -19,9 +19,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/DeleteFilterTest.cs b/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/DeleteFilterTest.cs deleted file mode 100644 index bfba9778a..000000000 --- a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/DeleteFilterTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Util.Tests.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 逻辑删除过滤器测试 - /// - public class DeleteFilterTest { - /// - /// 测试消息输出 - /// - private readonly ITestOutputHelper _testOutputHelper; - /// - /// 逻辑删除过滤器 - /// - private readonly DeleteFilter _filter; - - /// - /// 测试初始化 - /// - public DeleteFilterTest( ITestOutputHelper testOutputHelper ) { - _testOutputHelper = testOutputHelper; - _filter = new DeleteFilter(); - } - - /// - /// 测试是否启用 - /// - [Fact] - public void TestIsEnabled() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - _filter.Disable(); - Assert.False( _filter.IsEnabled ); - - //启用过滤器 - _filter.Enable(); - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试禁用 - /// - [Fact] - public void TestDisable() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - using( _filter.Disable() ) { - Assert.False( _filter.IsEnabled ); - } - - //恢复启用状态 - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filter.IsEntityEnabled() ); - Assert.True( _filter.IsEntityEnabled() ); - } - - /// - /// 测试获取表达式 - /// - [Fact] - public void TestGetExpression() { - var expression = _filter.GetExpression(); - var result = "entity => Not(Property(entity, \"IsDeleted\"))"; - _testOutputHelper.WriteLine( expression.ToString() ); - Assert.Equal( result,expression.ToString() ); - } - } -} diff --git a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/FilterManagerTest.cs b/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/FilterManagerTest.cs deleted file mode 100644 index 62514068c..000000000 --- a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/FilterManagerTest.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Util.Data.Filters; -using Util.Domain; -using Util.Tests.Models; -using Xunit; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 数据过滤器管理器测试 - /// - public class FilterManagerTest { - /// - /// 数据过滤器管理器 - /// - private readonly IFilterManager _filterManager; - - /// - /// 测试初始化 - /// - public FilterManagerTest( IFilterManager filterManager ) { - _filterManager = filterManager; - } - - /// - /// 测试获取过滤器 - 泛型 - /// - [Fact] - public void TestGetFilter_1() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(); - Assert.NotNull( filter ); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试获取过滤器 - 非泛型 - /// - [Fact] - public void TestGetFilter_2() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(typeof( IDelete ) ); - Assert.NotNull( filter ); - } - - /// - /// 测试禁用过滤器 - /// - [Fact] - public void TestDisableFilter_1() { - _filterManager.DisableFilter(); - var filter = _filterManager.GetFilter(); - Assert.False( filter.IsEnabled ); - } - - /// - /// 测试禁用过滤器 - 使用using - /// - [Fact] - public void TestDisableFilter_2() { - var filter = _filterManager.GetFilter(); - using ( _filterManager.DisableFilter() ) { - Assert.False( filter.IsEnabled ); - } - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试启用过滤器 - /// - [Fact] - public void TestEnableFilter() { - var filter = _filterManager.GetFilter(); - - //禁用 - _filterManager.DisableFilter(); - Assert.False( filter.IsEnabled ); - - //启用 - _filterManager.EnableFilter(); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filterManager.IsEntityEnabled() ); - Assert.True( _filterManager.IsEntityEnabled() ); - } - - /// - /// 测试过滤器是否启用 - /// - [Fact] - public void TestIsEnabled() { - Assert.True( _filterManager.IsEnabled() ); - Assert.False( _filterManager.IsEnabled() ); - } - } -} - diff --git a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj b/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj index c3988689f..e990d5700 100644 --- a/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj +++ b/test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/DeleteFilterTest.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/DeleteFilterTest.cs index 0828d6abf..aa824e841 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/DeleteFilterTest.cs +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/DeleteFilterTest.cs @@ -2,7 +2,7 @@ using Xunit; using Xunit.Abstractions; -namespace Util.Data.EntityFrameworkCore.Filters; +namespace Util.Data.EntityFrameworkCore.Filters; /// /// 逻辑删除过滤器测试 @@ -32,7 +32,7 @@ public DeleteFilterTest( ITestOutputHelper testOutputHelper ) { public void TestIsEnabled() { //默认值为启用 Assert.True( _filter.IsEnabled ); - + //禁用过滤器 _filter.Disable(); Assert.False( _filter.IsEnabled ); @@ -51,7 +51,7 @@ public void TestDisable() { Assert.True( _filter.IsEnabled ); //禁用过滤器 - using( _filter.Disable() ) { + using ( _filter.Disable() ) { Assert.False( _filter.IsEnabled ); } @@ -67,15 +67,4 @@ public void TestIsEntityEnabled() { Assert.False( _filter.IsEntityEnabled() ); Assert.True( _filter.IsEntityEnabled() ); } - - /// - /// 测试获取表达式 - /// - [Fact] - public void TestGetExpression() { - var expression = _filter.GetExpression(); - var result = "entity => Not(Property(entity, \"IsDeleted\"))"; - _testOutputHelper.WriteLine( expression.ToString() ); - Assert.Equal( result,expression.ToString() ); - } } \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/FilterManagerTest.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/FilterManagerTest.cs index 66c5e54be..a36ccdb56 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/FilterManagerTest.cs +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Filters/FilterManagerTest.cs @@ -1,4 +1,5 @@ -using Util.Data.Filters; +using Util.Data.EntityFrameworkCore.Samples; +using Util.Data.Filters; using Util.Domain; using Util.Tests.Models; using Xunit; @@ -97,4 +98,48 @@ public void TestIsEnabled() { Assert.True( _filterManager.IsEnabled() ); Assert.False( _filterManager.IsEnabled() ); } + + /// + /// 测试获取过滤表达式 - 1个过滤器 + /// + [Fact] + public void TestGetExpression_1() { + FilterManager.AddFilterType(); + FilterManager.RemoveFilterType(); + var expression = _filterManager.GetExpression( null ); + Assert.Equal( "t => False", expression?.ToString() ); + } + + /// + /// 测试获取过滤表达式 - 2个过滤器 + /// + [Fact] + public void TestGetExpression_2() { + FilterManager.AddFilterType(); + FilterManager.AddFilterType(); + var expression = _filterManager.GetExpression( null ); + Assert.Equal( "t => (False AndAlso True)", expression?.ToString() ); + } + + /// + /// 测试获取过滤表达式 - 2个过滤器 - 只启用一个过滤器 + /// + [Fact] + public void TestGetExpression_3() { + FilterManager.AddFilterType(); + FilterManager.AddFilterType(); + var expression = _filterManager.GetExpression( null ); + Assert.Equal( "t => True", expression?.ToString() ); + } + + /// + /// 测试获取过滤表达式 - 未启用过滤器 + /// + [Fact] + public void TestGetExpression_4() { + FilterManager.AddFilterType(); + FilterManager.AddFilterType(); + var expression = _filterManager.GetExpression( null ); + Assert.Null( expression ); + } } \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/Test.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/Test.cs new file mode 100644 index 000000000..221a7fb24 --- /dev/null +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/Test.cs @@ -0,0 +1,24 @@ +namespace Util.Data.EntityFrameworkCore.Samples; + +public interface ITest { + /// + /// 是否删除 + /// + bool IsDeleted { get; set; } +} + +public interface ITest2 { + /// + /// 租户标识 + /// + string TenantId { get; set; } +} + +public class Test:ITest,ITest2 { + public bool IsDeleted { get; set; } + public string TenantId { get; set; } +} + +public class Test2 : ITest2 { + public string TenantId { get; set; } +} \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/TestFilter.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/TestFilter.cs new file mode 100644 index 000000000..c255dfa0e --- /dev/null +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/TestFilter.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq.Expressions; +using Util.Data.EntityFrameworkCore.Filters; + +namespace Util.Data.EntityFrameworkCore.Samples; + +/// +/// 测试过滤器1 +/// +public class TestFilter1 : FilterBase { + /// + /// 获取过滤表达式 + /// + /// 实体类型 + public override Expression> GetExpression( object state ) where TEntity : class { + Expression> expression = t => false; + return expression; + } +} + +/// +/// 测试过滤器2 +/// +public class TestFilter2 : FilterBase { + /// + /// 获取过滤表达式 + /// + /// 实体类型 + public override Expression> GetExpression( object state ) where TEntity : class { + Expression> expression = t => true; + return expression; + } +} \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Startup.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Startup.cs index a81cfec9f..479f11780 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Startup.cs +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Startup.cs @@ -3,6 +3,7 @@ using Util.Aop; using Util.Helpers; using Util.Sessions; +using Util.Tenants; using Util.Tests.Infrastructure; using Util.Tests.UnitOfWorks; using Xunit.DependencyInjection.Logging; @@ -21,6 +22,7 @@ public void ConfigureHost( IHostBuilder hostBuilder ) { hostBuilder.ConfigureDefaults( null ) .AsBuild() .AddAop() + .AddTenant() .AddSqlServerUnitOfWork( Config.GetConnectionString( "connection" ) ) .AddUtil(); } diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/EntityEventsTest.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/EntityEventsTest.cs index 04db7344c..e7faad267 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/EntityEventsTest.cs +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/EntityEventsTest.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Util.Events; +using Util.Exceptions; using Util.Tests.EventHandlers; using Util.Tests.Events; using Util.Tests.Fakes; @@ -219,6 +220,74 @@ public async Task TestEntityChangedEvent_Deleted_2() { #endregion + #region 领域事件 + + /// + /// 测试领域事件 - 添加一个本地事件 + /// + [Fact] + public async Task TestDomainEvent_1() { + //添加实体 + var entity = ProductFakeService.GetProduct(); + entity.Init(); + await _productRepository.AddAsync( entity ); + await UnitOfWork.CommitAsync(); + + var code = $"TestDomainEvent_{entity.Id}"; + try { + entity = await _productRepository.FindByIdAsync( entity.Id ); + entity.Code = code; + entity.TestDomainEvent1(); + + //保存前触发事件处理器,抛出异常 + await UnitOfWork.CommitAsync(); + } + catch ( Warning ex ) { + //事件处理器抛出异常消息为实体ID + Assert.Equal( entity.Id.ToString(), ex.Message ); + + //保存前触发事件,所以没有保存成功 + entity = await _productRepository.SingleAsync( t => t.Code == code ); + Assert.Null( entity ); + return; + } + Assert.Fail(); + } + + /// + /// 测试领域事件 - 添加一个集成事件 + /// + [Fact] + public async Task TestDomainEvent_2() { + //添加实体 + var entity = ProductFakeService.GetProduct(); + entity.Init(); + await _productRepository.AddAsync( entity ); + await UnitOfWork.CommitAsync(); + + var code = $"TestDomainEvent_{entity.Id}"; + try { + entity = await _productRepository.FindByIdAsync( entity.Id ); + entity.Code = code; + entity.TestDomainEvent2(); + + //保存后触发事件处理器,抛出异常 + await UnitOfWork.CommitAsync(); + } + catch ( Warning ex ) { + //事件处理器抛出异常消息为实体ID + Assert.Equal( entity.Id.ToString(), ex.Message ); + + //保存后触发事件,所以保存成功 + entity = await _productRepository.SingleAsync( t => t.Code == code ); + Assert.NotNull( entity ); + return; + } + Assert.Fail(); + } + + #endregion + #region TestEventBus_ServiceLifetime_Transient /// @@ -245,7 +314,7 @@ public TestEventHandler( IProductRepository applicationRepository ) { public override async Task HandleAsync( TestEvent @event, CancellationToken cancellationToken ) { var entity = await _repository.FindByIdAsync( @event.Id, cancellationToken ); Assert.NotNull( entity ); - Assert.Equal( "Name",entity.Name ); + Assert.Equal( "Name", entity.Name ); } } diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/ProductRepositoryTest.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/ProductRepositoryTest.cs index 784ea3fd2..f17879dab 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/ProductRepositoryTest.cs +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/ProductRepositoryTest.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Threading.Tasks; using Util.Exceptions; +using Util.Helpers; +using Util.Tenants.Managements; using Util.Tests.Fakes; using Util.Tests.Infrastructure; using Util.Tests.Models; @@ -12,7 +14,7 @@ using Xunit.Abstractions; using Xunit.DependencyInjection; -namespace Util.Data.EntityFrameworkCore.Tests; +namespace Util.Data.EntityFrameworkCore.Tests; /// /// 产品仓储测试 @@ -20,6 +22,8 @@ namespace Util.Data.EntityFrameworkCore.Tests; /// 1. 测试Guid类型标识 /// 2. 测试仓储基础方法 /// 3. 测试扩展属性 +/// 4. 测试逻辑删除过滤器 +/// 5. 测试租户过滤器 /// public partial class ProductRepositoryTest : TestBase { @@ -203,7 +207,7 @@ public async Task TestFindByIds_1() { await UnitOfWork.CommitAsync(); //验证 - var result = (await _repository.FindByIdsAsync( products[0].Id, products[1].Id, products[2].Id )).OrderBy( t => t.Code ).ToList(); + var result = ( await _repository.FindByIdsAsync( products[0].Id, products[1].Id, products[2].Id ) ).OrderBy( t => t.Code ).ToList(); Assert.Equal( products[0].Description, result[0].Description ); Assert.Equal( products[1].Description, result[1].Description ); Assert.Equal( products[2].Description, result[2].Description ); @@ -226,7 +230,7 @@ public async Task TestFindByIds_2() { await UnitOfWork.CommitAsync(); //验证 - var result = (await _repository.FindByIdsAsync( products.Select( t => t.Id ) )).OrderBy( t => t.Code ).ToList(); + var result = ( await _repository.FindByIdsAsync( products.Select( t => t.Id ) ) ).OrderBy( t => t.Code ).ToList(); Assert.Equal( products[0].Description, result[0].Description ); Assert.Equal( products[1].Description, result[1].Description ); Assert.Equal( products[2].Description, result[2].Description ); @@ -249,7 +253,7 @@ public async Task TestFindByIds_3() { await UnitOfWork.CommitAsync(); //验证 - var result = (await _repository.FindByIdsAsync( products.Select( t => t.Id ).Join() )).OrderBy( t => t.Code ).ToList(); + var result = ( await _repository.FindByIdsAsync( products.Select( t => t.Id ).Join() ) ).OrderBy( t => t.Code ).ToList(); Assert.Equal( products[0].Description, result[0].Description ); Assert.Equal( products[1].Description, result[1].Description ); Assert.Equal( products[2].Description, result[2].Description ); @@ -1495,4 +1499,81 @@ public async Task TestExtraProperties_15() { } #endregion + + #region 测试租户过滤器 + + /// + /// 测试租户过滤器 + /// + [Fact] + public async Task TestTenantFilter_1() { + //变量定义 + var name = $"TestTenantFilter_{Id.Create()}"; + var tenantId = "tenant_1"; + var tenantId2 = "tenant_2"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //添加实体1 + var entity = await AddProductAsync( name ); + UnitOfWork.ClearCache(); + + //验证租户标识已设置 + Assert.Equal( tenantId, entity.TenantId ); + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId2; + + //添加实体2 + entity = await AddProductAsync( name ); + UnitOfWork.ClearCache(); + + //验证租户标识已设置 + Assert.Equal( tenantId2, entity.TenantId ); + + //查询 + var result = await _repository.FindAllAsync( t => t.Name == name ); + Assert.Single( result ); + Assert.Equal( tenantId2, result[0].TenantId ); + } + + /// + /// 测试租户过滤器 - 禁用租户过滤器 + /// + [Fact] + public async Task TestTenantFilter_2() { + //变量定义 + var name = $"TestTenantFilter_{Id.Create()}"; + var tenantId = "tenant_1"; + var tenantId2 = "tenant_2"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //添加实体1 + var entity = await AddProductAsync( name ); + UnitOfWork.ClearCache(); + + //验证租户标识已设置 + Assert.Equal( tenantId, entity.TenantId ); + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId2; + + //添加实体2 + entity = await AddProductAsync( name ); + UnitOfWork.ClearCache(); + + //验证租户标识已设置 + Assert.Equal( tenantId2, entity.TenantId ); + + //查询 + using ( _repository.DisableTenantFilter() ) { + var result = await _repository.FindAllAsync( t => t.Name == name ); + Assert.Equal( 2, result.Count ); + } + } + + #endregion } \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/UnitOfWorkActionManagerTest.cs b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/UnitOfWorkActionManagerTest.cs deleted file mode 100644 index 88f8f80c8..000000000 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/UnitOfWorkActionManagerTest.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Util.Events; -using Util.Tests.Events; -using Util.Tests.Fakes; -using Util.Tests.Infrastructure; -using Util.Tests.Repositories; -using Util.Tests.UnitOfWorks; -using Xunit; -using Xunit.Abstractions; - -namespace Util.Data.EntityFrameworkCore.Tests; - -/// -/// 工作单元操作管理器测试 -/// -public class UnitOfWorkActionManagerTest : TestBase { - /// - /// 测试输出 - /// - private readonly ITestOutputHelper _testOutputHelper; - /// - /// 产品仓储 - /// - private readonly IProductRepository _productRepository; - /// - /// 测试事件总线 - /// - private readonly ITestEventBus _eventBus; - - /// - /// 测试初始化 - /// - public UnitOfWorkActionManagerTest( ITestOutputHelper testOutputHelper, IProductRepository productRepository, - ITestUnitOfWork unitOfWork, ITestEventBus eventBus ) :base(unitOfWork){ - _testOutputHelper = testOutputHelper; - _productRepository = productRepository; - _eventBus = eventBus; - } - - /// - /// 测试执行操作 - 提交成功,执行操作 - /// - [Fact] - public async Task TestExecuteAsync_1() { - //添加实体 - var entity = ProductFakeService.GetProduct(); - entity.Init(); - entity.Name = "TestExecuteAsync_1"; - - //在提交前发送事件 - var @event = new TestEvent { Name = "1" }; - await _eventBus.PublishAsync( @event ); - - await _productRepository.AddAsync( entity ); - await UnitOfWork.CommitAsync(); - - //提交成功后执行事件处理器 - Assert.Equal( "12", @event.Name ); - } - - /// - /// 测试执行操作 - 提交失败,不执行操作 - /// - [Fact] - public async Task TestExecuteAsync_2() { - //添加实体 - var entity = ProductFakeService.GetProduct(); - entity.Init(); - entity.Name = "TestExecuteAsync_2"; - - //在提交前发送事件 - var @event = new TestEvent { Name = "1" }; - await _eventBus.PublishAsync( @event ); - - try { - await _productRepository.AddAsync( entity ); - throw new Exception(); - await UnitOfWork.CommitAsync(); - } - catch ( Exception e ) { - //提交失败不执行事件处理器 - Assert.Equal( "1", @event.Name ); - } - } - - /// - /// 测试执行操作 - 多次提交成功,应只执行一次操作 - /// - [Fact] - public async Task TestExecuteAsync_3() { - //添加实体 - var entity = ProductFakeService.GetProduct(); - entity.Init(); - entity.Name = "TestExecuteAsync_3_1"; - - //在提交前发送事件 - var @event = new TestEvent { Name = "1" }; - await _eventBus.PublishAsync( @event ); - - await _productRepository.AddAsync( entity ); - await UnitOfWork.CommitAsync(); - - //修改后提交 - entity = await _productRepository.FindByIdAsync( entity.Id ); - entity.Name = "TestExecuteAsync_3_2"; - await UnitOfWork.CommitAsync(); - - //提交成功后执行事件处理器 - Assert.Equal( "12", @event.Name ); - } -} - -/// -/// 测试事件处理器 -/// -public class TestEventHandler : EventHandlerBase { - public override Task HandleAsync( TestEvent @event, CancellationToken cancellationToken ) { - @event.Name += "2"; - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration.csproj b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration.csproj index f238a6206..9371adfb9 100644 --- a/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration.csproj +++ b/test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration.csproj @@ -28,13 +28,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/DeleteFilterTest.cs b/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/DeleteFilterTest.cs deleted file mode 100644 index bfba9778a..000000000 --- a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/DeleteFilterTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Util.Tests.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 逻辑删除过滤器测试 - /// - public class DeleteFilterTest { - /// - /// 测试消息输出 - /// - private readonly ITestOutputHelper _testOutputHelper; - /// - /// 逻辑删除过滤器 - /// - private readonly DeleteFilter _filter; - - /// - /// 测试初始化 - /// - public DeleteFilterTest( ITestOutputHelper testOutputHelper ) { - _testOutputHelper = testOutputHelper; - _filter = new DeleteFilter(); - } - - /// - /// 测试是否启用 - /// - [Fact] - public void TestIsEnabled() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - _filter.Disable(); - Assert.False( _filter.IsEnabled ); - - //启用过滤器 - _filter.Enable(); - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试禁用 - /// - [Fact] - public void TestDisable() { - //默认值为启用 - Assert.True( _filter.IsEnabled ); - - //禁用过滤器 - using( _filter.Disable() ) { - Assert.False( _filter.IsEnabled ); - } - - //恢复启用状态 - Assert.True( _filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filter.IsEntityEnabled() ); - Assert.True( _filter.IsEntityEnabled() ); - } - - /// - /// 测试获取表达式 - /// - [Fact] - public void TestGetExpression() { - var expression = _filter.GetExpression(); - var result = "entity => Not(Property(entity, \"IsDeleted\"))"; - _testOutputHelper.WriteLine( expression.ToString() ); - Assert.Equal( result,expression.ToString() ); - } - } -} diff --git a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/FilterManagerTest.cs b/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/FilterManagerTest.cs deleted file mode 100644 index 62514068c..000000000 --- a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/FilterManagerTest.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Util.Data.Filters; -using Util.Domain; -using Util.Tests.Models; -using Xunit; - -namespace Util.Data.EntityFrameworkCore.Filters { - /// - /// 数据过滤器管理器测试 - /// - public class FilterManagerTest { - /// - /// 数据过滤器管理器 - /// - private readonly IFilterManager _filterManager; - - /// - /// 测试初始化 - /// - public FilterManagerTest( IFilterManager filterManager ) { - _filterManager = filterManager; - } - - /// - /// 测试获取过滤器 - 泛型 - /// - [Fact] - public void TestGetFilter_1() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(); - Assert.NotNull( filter ); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试获取过滤器 - 非泛型 - /// - [Fact] - public void TestGetFilter_2() { - //逻辑删除过滤器已默认加载 - var filter = _filterManager.GetFilter(typeof( IDelete ) ); - Assert.NotNull( filter ); - } - - /// - /// 测试禁用过滤器 - /// - [Fact] - public void TestDisableFilter_1() { - _filterManager.DisableFilter(); - var filter = _filterManager.GetFilter(); - Assert.False( filter.IsEnabled ); - } - - /// - /// 测试禁用过滤器 - 使用using - /// - [Fact] - public void TestDisableFilter_2() { - var filter = _filterManager.GetFilter(); - using ( _filterManager.DisableFilter() ) { - Assert.False( filter.IsEnabled ); - } - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试启用过滤器 - /// - [Fact] - public void TestEnableFilter() { - var filter = _filterManager.GetFilter(); - - //禁用 - _filterManager.DisableFilter(); - Assert.False( filter.IsEnabled ); - - //启用 - _filterManager.EnableFilter(); - Assert.True( filter.IsEnabled ); - } - - /// - /// 测试实体是否启用过滤器 - /// - [Fact] - public void TestIsEntityEnabled() { - Assert.False( _filterManager.IsEntityEnabled() ); - Assert.True( _filterManager.IsEntityEnabled() ); - } - - /// - /// 测试过滤器是否启用 - /// - [Fact] - public void TestIsEnabled() { - Assert.True( _filterManager.IsEnabled() ); - Assert.False( _filterManager.IsEnabled() ); - } - } -} - diff --git a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration.csproj b/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration.csproj index e89e247b1..1c9762950 100644 --- a/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration.csproj +++ b/test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration.csproj @@ -18,9 +18,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.Sql.Tests/Util.Data.Sql.Tests.csproj b/test/Util.Data.Sql.Tests/Util.Data.Sql.Tests.csproj index 7d6546011..992ade611 100644 --- a/test/Util.Data.Sql.Tests/Util.Data.Sql.Tests.csproj +++ b/test/Util.Data.Sql.Tests/Util.Data.Sql.Tests.csproj @@ -6,7 +6,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Data.Tests/Util.Data.Tests.csproj b/test/Util.Data.Tests/Util.Data.Tests.csproj index 43f33fef9..6cd950687 100644 --- a/test/Util.Data.Tests/Util.Data.Tests.csproj +++ b/test/Util.Data.Tests/Util.Data.Tests.csproj @@ -6,7 +6,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Domain.Tests/Util.Domain.Tests.csproj b/test/Util.Domain.Tests/Util.Domain.Tests.csproj index 151525306..644a1f0a4 100644 --- a/test/Util.Domain.Tests/Util.Domain.Tests.csproj +++ b/test/Util.Domain.Tests/Util.Domain.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Util.Events.MediatR.Tests.Integration/Local/EventBusExtensionsTest.cs b/test/Util.Events.MediatR.Tests.Integration/Local/EventBusExtensionsTest.cs new file mode 100644 index 000000000..81c6d7280 --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Local/EventBusExtensionsTest.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Util.Events.Tests.Samples; +using Xunit; + +namespace Util.Events.Tests.Local; + +/// +/// 本地事件总线扩展测试 +/// +public class EventBusExtensionsTest { + /// + /// 本地事件总线 + /// + private readonly ILocalEventBus _eventBus; + + /// + /// 测试初始化 + /// + public EventBusExtensionsTest( ILocalEventBus eventBus ) { + _eventBus = eventBus; + } + + /// + /// 测试发布事件集合 + /// + [Fact] + public async Task TestPublishAsync() { + var event1 = new EventSample { Value = "a" }; + var event2 = new EventSample2(); + var event3 = new EventSample3(); + var events = new List { event1, event2, event3 }; + await _eventBus.PublishAsync( events ); + Assert.Equal( "1:a", event1.Result ); + Assert.Equal( "2", event2.Result ); + Assert.Equal( "3", event3.Result ); + } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Local/EventBusTest.cs b/test/Util.Events.MediatR.Tests.Integration/Local/EventBusTest.cs new file mode 100644 index 000000000..5e035d7c0 --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Local/EventBusTest.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Util.Events.Tests.Samples; + +namespace Util.Events.Tests.Local; + +/// +/// 事件总线测试 +/// +public class EventBusTest { + /// + /// 事件总线 + /// + private readonly IEventBus _eventBus; + + /// + /// 测试初始化 + /// + public EventBusTest( IEventBus eventBus ) { + _eventBus = eventBus; + } + + /// + /// 测试发布事件 + /// + [Fact] + public async Task TestPublishAsync() { + var @event = new EventSample { Value = "a" }; + await _eventBus.PublishAsync( @event ); + Assert.Equal( "1:a", @event.Result ); + } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Local/LocalEventBusTest.cs b/test/Util.Events.MediatR.Tests.Integration/Local/LocalEventBusTest.cs new file mode 100644 index 000000000..fb9112137 --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Local/LocalEventBusTest.cs @@ -0,0 +1,43 @@ +using System.Threading; +using System.Threading.Tasks; +using Util.Events.Tests.Samples; + +namespace Util.Events.Tests.Local; + +/// +/// 本地事件总线测试 +/// +public class LocalEventBusTest { + /// + /// 本地事件总线 + /// + private readonly ILocalEventBus _eventBus; + + /// + /// 测试初始化 + /// + public LocalEventBusTest( ILocalEventBus eventBus ) { + _eventBus = eventBus; + } + + /// + /// 测试发布事件 - 传入事件参数是具体事件类型 + /// + [Fact] + public async Task TestPublishAsync() { + var token = new CancellationTokenSource().Token; + var @event = new EventSample { Value = "a" }; + await _eventBus.PublishAsync( @event, token ); + Assert.Equal( "1:a", @event.Result ); + } + + /// + /// 测试发布事件 - 传入事件参数是IEvent接口 + /// + [Fact] + public async Task TestPublishAsync_2() { + IEvent @event = new EventSample { Value = "a" }; + await _eventBus.PublishAsync( @event ); + Assert.Equal( "1:a", ((EventSample)@event).Result ); + } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Samples/EventHandlerSample.cs b/test/Util.Events.MediatR.Tests.Integration/Samples/EventHandlerSample.cs new file mode 100644 index 000000000..f2bf8838c --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Samples/EventHandlerSample.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Util.Events.Tests.Samples; + +/// +/// 本地事件处理器样例 +/// +public class EventHandlerSample : EventHandlerBase { + /// + /// 处理事件 + /// + /// 事件 + /// 取消令牌 + public override Task HandleAsync( EventSample @event, CancellationToken cancellationToken = default ) { + @event.Result = $"1:{@event.Value}"; + return Task.CompletedTask; + } +} + +/// +/// 本地事件处理器样例2 +/// +public class EventHandlerSample2 : EventHandlerBase { + /// + /// 处理事件 + /// + /// 事件 + /// 取消令牌 + public override Task HandleAsync( EventSample2 @event, CancellationToken cancellationToken = default ) { + @event.Result += "2"; + return Task.CompletedTask; + } +} + + +/// +/// 本地事件处理器样例4 +/// +public class EventHandlerSample4 : EventHandlerBase { + /// + /// 处理事件 + /// + /// 事件 + /// 取消令牌 + public override Task HandleAsync( EventSample3 @event, CancellationToken cancellationToken = default ) { + @event.Result += "3"; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Samples/EventSample.cs b/test/Util.Events.MediatR.Tests.Integration/Samples/EventSample.cs new file mode 100644 index 000000000..8d37b454b --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Samples/EventSample.cs @@ -0,0 +1,35 @@ +namespace Util.Events.Tests.Samples; + +/// +/// 本地事件样例 +/// +public class EventSample : EventBase { + /// + /// 值 + /// + public string Value { get; set; } + /// + /// 结果 + /// + public string Result { get; set; } +} + +/// +/// 本地事件样例2 +/// +public class EventSample2 : EventBase { + /// + /// 结果 + /// + public string Result { get; set; } +} + +/// +/// 本地事件样例3 +/// +public class EventSample3 : EventBase { + /// + /// 结果 + /// + public string Result { get; set; } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Startup.cs b/test/Util.Events.MediatR.Tests.Integration/Startup.cs new file mode 100644 index 000000000..65f5469ea --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Startup.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit.DependencyInjection.Logging; + +namespace Util.Events.Tests; + +/// +/// +/// +public class Startup { + /// + /// + /// + public void ConfigureHost( IHostBuilder hostBuilder ) { + hostBuilder + .AsBuild() + .AddMediatR() + .AddUtil(); + } + + /// + /// ÷ + /// + public void ConfigureServices( IServiceCollection services ) { + services.AddLogging( logBuilder => logBuilder.AddXunitOutput() ); + } +} \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Usings.cs b/test/Util.Events.MediatR.Tests.Integration/Usings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Util.Events.MediatR.Tests.Integration/Util.Events.MediatR.Tests.Integration.csproj b/test/Util.Events.MediatR.Tests.Integration/Util.Events.MediatR.Tests.Integration.csproj new file mode 100644 index 000000000..05f6b32ce --- /dev/null +++ b/test/Util.Events.MediatR.Tests.Integration/Util.Events.MediatR.Tests.Integration.csproj @@ -0,0 +1,30 @@ + + + + $(NetTargetFramework) + false + Util.Events.Tests + Util.Events.Tests + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Util.Events.Tests.Integration/Startup.cs b/test/Util.Events.Tests.Integration/Startup.cs index 0d6f8bd6c..fd9a95373 100644 --- a/test/Util.Events.Tests.Integration/Startup.cs +++ b/test/Util.Events.Tests.Integration/Startup.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Util.Aop; using Xunit.DependencyInjection.Logging; namespace Util.Events.Tests; @@ -15,7 +14,6 @@ public class Startup { public void ConfigureHost( IHostBuilder hostBuilder ) { hostBuilder .AsBuild() - .AddAop() .AddUtil(); } diff --git a/test/Util.Events.Tests.Integration/Util.Events.Tests.Integration.csproj b/test/Util.Events.Tests.Integration/Util.Events.Tests.Integration.csproj index 318296aa3..6ceed1271 100644 --- a/test/Util.Events.Tests.Integration/Util.Events.Tests.Integration.csproj +++ b/test/Util.Events.Tests.Integration/Util.Events.Tests.Integration.csproj @@ -1,17 +1,17 @@ - + $(NetTargetFramework) false Util.Events.Tests - Util.Events.Tests.Dapr + Util.Events.Tests - + - + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,7 +24,6 @@ - diff --git a/test/Util.FileStorage.Minio.Tests.Integration/Samples/TestSession.cs b/test/Util.FileStorage.Minio.Tests.Integration/Samples/TestSession.cs index a8e7eb892..59cd71929 100644 --- a/test/Util.FileStorage.Minio.Tests.Integration/Samples/TestSession.cs +++ b/test/Util.FileStorage.Minio.Tests.Integration/Samples/TestSession.cs @@ -7,7 +7,9 @@ namespace Util.FileStorage.Minio.Samples; /// 测试用户会话 /// public class TestSession : ISession { + public IServiceProvider ServiceProvider => null; public static Guid TestUserId = new ( "2af5e99e-391c-451b-9112-f7a3eb9b0a55" ); public bool IsAuthenticated => true; public string UserId => TestUserId.ToString(); + public string TenantId => string.Empty; } \ No newline at end of file diff --git a/test/Util.FileStorage.Minio.Tests.Integration/Util.FileStorage.Minio.Tests.Integration.csproj b/test/Util.FileStorage.Minio.Tests.Integration/Util.FileStorage.Minio.Tests.Integration.csproj index 13901e282..86af25507 100644 --- a/test/Util.FileStorage.Minio.Tests.Integration/Util.FileStorage.Minio.Tests.Integration.csproj +++ b/test/Util.FileStorage.Minio.Tests.Integration/Util.FileStorage.Minio.Tests.Integration.csproj @@ -10,9 +10,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Generators.Razor.Tests.Integration/Util.Generators.Razor.Tests.Integration.csproj b/test/Util.Generators.Razor.Tests.Integration/Util.Generators.Razor.Tests.Integration.csproj index a392fbfc2..5e01df39e 100644 --- a/test/Util.Generators.Razor.Tests.Integration/Util.Generators.Razor.Tests.Integration.csproj +++ b/test/Util.Generators.Razor.Tests.Integration/Util.Generators.Razor.Tests.Integration.csproj @@ -15,9 +15,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Generators.Tests/Configuration/GeneratorOptionsBuilderTest.cs b/test/Util.Generators.Tests/Configuration/GeneratorOptionsBuilderTest.cs index b3c0c5184..968d832fa 100644 --- a/test/Util.Generators.Tests/Configuration/GeneratorOptionsBuilderTest.cs +++ b/test/Util.Generators.Tests/Configuration/GeneratorOptionsBuilderTest.cs @@ -47,6 +47,7 @@ public void TestProjects_1() { Assert.True( project.Enabled ); Assert.True( project.Utc ); Assert.True( project.I18n ); + Assert.True( project.EnableSchema ); Assert.Equal( "1", project.Extend ); } @@ -68,6 +69,7 @@ public void TestProjects_2() { Assert.False( project.Enabled ); Assert.False( project.Utc ); Assert.False( project.I18n ); + Assert.False( project.EnableSchema ); Assert.Equal( "2", project.Extend ); } diff --git a/test/Util.Generators.Tests/Contexts/GeneratorContextBuilderTest.cs b/test/Util.Generators.Tests/Contexts/GeneratorContextBuilderTest.cs index c71d19d4b..3f4051b34 100644 --- a/test/Util.Generators.Tests/Contexts/GeneratorContextBuilderTest.cs +++ b/test/Util.Generators.Tests/Contexts/GeneratorContextBuilderTest.cs @@ -173,6 +173,17 @@ public void TestProjectContext_ApiPort() { Assert.Equal( "456", projectContext2.ApiPort ); } + /// + /// 测试是否启用架构 + /// + [Fact] + public void TestProjectContext_EnableSchema() { + var projectContext = _context.Projects[0]; + Assert.True( projectContext.EnableSchema ); + var projectContext2 = _context.Projects[1]; + Assert.True( projectContext2.EnableSchema ); + } + /// /// 测试项目扩展 /// diff --git a/test/Util.Generators.Tests/Contexts/ProjectContextTest.cs b/test/Util.Generators.Tests/Contexts/ProjectContextTest.cs index 50b8d214f..7d30dab7e 100644 --- a/test/Util.Generators.Tests/Contexts/ProjectContextTest.cs +++ b/test/Util.Generators.Tests/Contexts/ProjectContextTest.cs @@ -32,12 +32,10 @@ public void TestClone() { Enabled = true, Utc = true, I18n = true, + EnableSchema = true, ProjectType = ProjectType.Ui, ApiPort = "80", - Extend = new TestExtend { - Id = "1", - Name = "Name" - } + Extend = "{\"Id\":\"1\",\"Name\":\"Name\"}" }; //添加架构列表 @@ -69,6 +67,7 @@ public void TestClone() { Assert.Equal( projectContext.I18n, clone.I18n ); Assert.Equal( projectContext.ProjectType, clone.ProjectType ); Assert.Equal( projectContext.ApiPort, clone.ApiPort ); + Assert.Equal( projectContext.EnableSchema, clone.EnableSchema ); Assert.Equal( projectContext.GetExtend().Id, clone.GetExtend().Id ); Assert.Equal( projectContext.GetExtend().Name, clone.GetExtend().Name ); diff --git a/test/Util.Generators.Tests/Mocks/MockGeneratorOptionsBuilder.cs b/test/Util.Generators.Tests/Mocks/MockGeneratorOptionsBuilder.cs index e8fc91362..b13c0ad56 100644 --- a/test/Util.Generators.Tests/Mocks/MockGeneratorOptionsBuilder.cs +++ b/test/Util.Generators.Tests/Mocks/MockGeneratorOptionsBuilder.cs @@ -35,6 +35,7 @@ public Task BuildAsync() { }, ProjectType = ProjectType.WebApi, ApiPort = "123", + EnableSchema = true, Extend = "Extend1" }}, {"Test2",new ProjectOptions { @@ -45,6 +46,7 @@ public Task BuildAsync() { Enabled = true, Utc = true, I18n = true, + EnableSchema = true, Client = { AppName = "ClientApp2", Port = "2" @@ -62,6 +64,7 @@ public Task BuildAsync() { Enabled = false, Utc = false, I18n = false, + EnableSchema = false, Client = { AppName = "ClientApp3", Port = "3" diff --git a/test/Util.Generators.Tests/Util.Generators.Tests.csproj b/test/Util.Generators.Tests/Util.Generators.Tests.csproj index 6406ed0ad..b2a05329d 100644 --- a/test/Util.Generators.Tests/Util.Generators.Tests.csproj +++ b/test/Util.Generators.Tests/Util.Generators.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Util.Generators.Tests/appsettings.json b/test/Util.Generators.Tests/appsettings.json index 2bc59af39..426cae61d 100644 --- a/test/Util.Generators.Tests/appsettings.json +++ b/test/Util.Generators.Tests/appsettings.json @@ -13,6 +13,7 @@ "I18n": "true", "ProjectType": "WebApi", "ApiPort": "123", + "EnableSchema": "true", "Client": { "AppName": "ClientAppName_Test", "Port": "1" @@ -30,6 +31,7 @@ "I18n": "false", "ProjectType": "Ui", "ApiPort": "456", + "EnableSchema": "false", "Client": { "AppName": "ClientAppName_Test2", "Port": "2" diff --git a/test/Util.Images.Avatar.Tests.Integration/Util.Images.Avatar.Tests.Integration.csproj b/test/Util.Images.Avatar.Tests.Integration/Util.Images.Avatar.Tests.Integration.csproj index d2b036d76..daf2a320b 100644 --- a/test/Util.Images.Avatar.Tests.Integration/Util.Images.Avatar.Tests.Integration.csproj +++ b/test/Util.Images.Avatar.Tests.Integration/Util.Images.Avatar.Tests.Integration.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/test/Util.Images.ImageSharp.Tests.Integration/Util.Images.ImageSharp.Tests.Integration.csproj b/test/Util.Images.ImageSharp.Tests.Integration/Util.Images.ImageSharp.Tests.Integration.csproj index f9573e72b..809d7b611 100644 --- a/test/Util.Images.ImageSharp.Tests.Integration/Util.Images.ImageSharp.Tests.Integration.csproj +++ b/test/Util.Images.ImageSharp.Tests.Integration/Util.Images.ImageSharp.Tests.Integration.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/test/Util.Localization.Tests.Integration/Util.Localization.Tests.Integration.csproj b/test/Util.Localization.Tests.Integration/Util.Localization.Tests.Integration.csproj index 6ee0e6089..6000f9ab3 100644 --- a/test/Util.Localization.Tests.Integration/Util.Localization.Tests.Integration.csproj +++ b/test/Util.Localization.Tests.Integration/Util.Localization.Tests.Integration.csproj @@ -9,10 +9,10 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Logging.Serilog.Exceptionless.Tests.Integration/Util.Logging.Serilog.Exceptionless.Tests.Integration.csproj b/test/Util.Logging.Serilog.Exceptionless.Tests.Integration/Util.Logging.Serilog.Exceptionless.Tests.Integration.csproj index 801e9f21b..aeb21b559 100644 --- a/test/Util.Logging.Serilog.Exceptionless.Tests.Integration/Util.Logging.Serilog.Exceptionless.Tests.Integration.csproj +++ b/test/Util.Logging.Serilog.Exceptionless.Tests.Integration/Util.Logging.Serilog.Exceptionless.Tests.Integration.csproj @@ -9,9 +9,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Logging.Serilog.Tests.Integration/Util.Logging.Serilog.Tests.Integration.csproj b/test/Util.Logging.Serilog.Tests.Integration/Util.Logging.Serilog.Tests.Integration.csproj index dd23a44fd..24ad3fefb 100644 --- a/test/Util.Logging.Serilog.Tests.Integration/Util.Logging.Serilog.Tests.Integration.csproj +++ b/test/Util.Logging.Serilog.Tests.Integration/Util.Logging.Serilog.Tests.Integration.csproj @@ -9,9 +9,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Logging.Tests/Util.Logging.Tests.csproj b/test/Util.Logging.Tests/Util.Logging.Tests.csproj index 23149ae61..e05082c5c 100644 --- a/test/Util.Logging.Tests/Util.Logging.Tests.csproj +++ b/test/Util.Logging.Tests/Util.Logging.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Util.Microservices.Dapr.PubsubSample/Controllers/IntegrationEventController.cs b/test/Util.Microservices.Dapr.PubsubSample/Controllers/IntegrationEventController.cs index 87d39ec2d..356ed3f89 100644 --- a/test/Util.Microservices.Dapr.PubsubSample/Controllers/IntegrationEventController.cs +++ b/test/Util.Microservices.Dapr.PubsubSample/Controllers/IntegrationEventController.cs @@ -11,7 +11,7 @@ public class IntegrationEventController : IntegrationEventControllerBase { /// /// 状态管理操作 /// - private readonly IStateManage _stateManage; + private readonly IStateManager _stateManager; /// /// 日志操作 /// @@ -20,8 +20,8 @@ public class IntegrationEventController : IntegrationEventControllerBase { /// /// 初始化集成事件控制器 /// - public IntegrationEventController( IStateManage stateManage, ILogger log ) { - _stateManage = stateManage; + public IntegrationEventController( IStateManager stateManager, ILogger log ) { + _stateManager = stateManager; _log = log; } @@ -32,7 +32,7 @@ public IntegrationEventController( IStateManage stateManage, ILogger Test1Async( TestEvent @event ) { _log.LogInformation( "========================================================Pubsub_Test1:{@TestEvent}", @event ); - await _stateManage.StoreName( "event-state-store" ).AddAsync( $"pubsub_{@event.Code}", @event ); + await _stateManager.StoreName( "event-state-store" ).AddAsync( $"pubsub_{@event.Code}", @event ); return Success(); } diff --git a/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.EventLog.cs b/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.EventLog.cs index e918344ef..beef3acca 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.EventLog.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.EventLog.cs @@ -13,13 +13,28 @@ public partial class PubsubTest { /// [Fact] public async Task TestIntegrationEventLog_Published() { + //清空集成事件计数 + await _eventManager.ClearCountAsync(); + //发布事件,没有订阅者 var testEvent = new TestEvent( "1", "test" ); await _eventBus.Topic( "a" ).PublishAsync( testEvent ); //获取日志记录 - var log = await _eventLogStore.GetAsync( testEvent.EventId ); + var log = await _eventManager.GetAsync( testEvent.EventId ); Assert.True( log.State == EventState.Published ); + + //获取计数 + var count = await _eventManager.GetCountAsync(); + Assert.Equal( 1,count ); + + //再次发布事件 + testEvent = new TestEvent( "1", "test" ); + await _eventBus.Topic( "a" ).PublishAsync( testEvent ); + + //获取计数 + count = await _eventManager.GetCountAsync(); + Assert.Equal( 2, count ); } /// diff --git a/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.cs b/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.cs index 839466ae4..04e58b700 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/Events/PubsubTest.cs @@ -16,7 +16,7 @@ public partial class PubsubTest { /// /// 状态管理 /// - private readonly IStateManage _stateManage; + private readonly IStateManager _stateManager; /// /// 集成事件日志记录存储器 /// @@ -33,11 +33,11 @@ public partial class PubsubTest { /// /// 测试初始化 /// - public PubsubTest( IIntegrationEventBus eventBus, IStateManage stateManage, IIntegrationEventLogStore eventLogStore, + public PubsubTest( IIntegrationEventBus eventBus, IStateManager stateManager, IIntegrationEventLogStore eventLogStore, IIntegrationEventManager eventManager,ILogger logger ) { _eventBus = eventBus; _eventManager = eventManager; - _stateManage = stateManage; + _stateManager = stateManager; _eventLogStore = eventLogStore; _logger = logger; } @@ -66,7 +66,7 @@ public async Task TestPublishAsync() { /// private async Task GetResult( string key ) { for ( int i = 0; i < 100; i++ ) { - var result = await _stateManage.StoreName( "event-state-store" ).GetAsync( key ); + var result = await _stateManager.StoreName( "event-state-store" ).GetAsync( key ); if ( result != null ) { _logger.LogInformation( "成功获取事件发布订阅测试数据: i={i},key={key},data={@data}", i, key, result ); return result; diff --git a/test/Util.Microservices.Dapr.Tests.Integration/Samples/CustomerDto.cs b/test/Util.Microservices.Dapr.Tests.Integration/Samples/CustomerDto.cs index 45e1e4087..95fe30c90 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/Samples/CustomerDto.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/Samples/CustomerDto.cs @@ -86,7 +86,7 @@ public class CustomerDto { } /// -/// 客户参数 +/// 客户参数2 /// public class CustomerDto2 : CustomerDto, IDataKey,IDataType, IETag { public string Id { get; set; } diff --git a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageOfTTest.cs b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerOfTTest.cs similarity index 67% rename from test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageOfTTest.cs rename to test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerOfTTest.cs index c856eb425..203997933 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageOfTTest.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerOfTTest.cs @@ -6,21 +6,21 @@ namespace Util.Microservices.Dapr.Tests.StateManagements; /// 状态管理测试 - 泛型接口测试 /// [Collection( "Global" )] -public class StateManageOfTTest { +public class StateManagerOfTTest { /// /// 状态管理操作 /// - private readonly IStateManage _stateManage; + private readonly IStateManager _stateManager; /// /// 日志 /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// 测试初始化 /// - public StateManageOfTTest( IStateManage stateManage, ILogger logger ) { - _stateManage = stateManage; + public StateManagerOfTTest( IStateManager stateManager, ILogger logger ) { + _stateManager = stateManager; _logger = logger; } @@ -35,14 +35,14 @@ public async Task TestQueryAsync_1() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.Equal( t => t.Code, code ).QueryAsync(); + var result = await _stateManager.Equal( t => t.Code, code ).QueryAsync(); Assert.Single( result ); Assert.Contains( result, t => t.Code == code ); } @@ -58,14 +58,14 @@ public async Task TestQueryAsync_2() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.In( t => t.Code, code, code2 ).QueryAsync(); + var result = await _stateManager.In( t => t.Code, code, code2 ).QueryAsync(); Assert.Equal( 2, result.Count ); Assert.Contains( result, t => t.Code == code ); Assert.Contains( result, t => t.Code == code2 ); diff --git a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.Query.cs b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.Query.cs similarity index 67% rename from test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.Query.cs rename to test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.Query.cs index 9dc2fd9e2..8db791cb1 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.Query.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.Query.cs @@ -6,7 +6,7 @@ namespace Util.Microservices.Dapr.Tests.StateManagements; /// /// 状态管理测试 - 查询测试 /// -public partial class StateManageTest { +public partial class StateManagerTest { #region GetAsync @@ -17,14 +17,14 @@ public partial class StateManageTest { public async Task TestGetAsync_1() { //添加数据1 var dto_1 = new CustomerDto2 { Code = "1" }; - var key_1 = await _stateManage.SaveAsync( dto_1 ); + var key_1 = await _stateManager.SaveAsync( dto_1 ); //添加数据2 var dto_2 = new CustomerDto2 { Code = "2" }; - var key_2 = await _stateManage.SaveAsync( dto_2 ); + var key_2 = await _stateManager.SaveAsync( dto_2 ); //获取数据并验证 - var result = await _stateManage.GetAsync( new[] { key_1, key_2 } ); + var result = await _stateManager.GetAsync( new[] { key_1, key_2 } ); Assert.Contains( result, t => t.Code == "1" ); Assert.Contains( result, t => t.Code == "2" ); } @@ -42,10 +42,10 @@ public async Task TestGetByIdAsync() { var dto = new CustomerDto2 { Code = "123" }; //添加数据 - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //获取数据并验证 - var result = await _stateManage.GetByIdAsync( dto.Id ); + var result = await _stateManager.GetByIdAsync( dto.Id ); Assert.Equal( "123", result.Code ); } @@ -64,20 +64,41 @@ public async Task TestGetAllAsync() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.GetAllAsync(); + var result = await _stateManager.GetAllAsync(); Assert.Contains( result, t => t.Code == code ); Assert.Contains( result, t => t.Code == code2 ); } #endregion + #region SingleAsync + + /// + /// 测试获取指定类型的单条数据 + /// + [Fact] + public async Task TestSingleAsync() { + //变量定义 + var code = $"TestSingleAsync_{Id.Create()}"; + + //添加数据 + var dto = new CustomerDto2 { Code = code }; + await _stateManager.SaveAsync( dto ); + + //获取数据并验证 + var result = await _stateManager.Equal( t => t.Code, code ).SingleAsync(); + Assert.Equal( code, result.Code ); + } + + #endregion + #region QueryAsync /// @@ -91,14 +112,14 @@ public async Task TestQueryAsync_1() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.Equal( "code", code ).QueryAsync(); + var result = await _stateManager.Equal( "code", code ).QueryAsync(); Assert.Single( result ); Assert.Contains( result, t => t.Code == code ); } @@ -114,14 +135,14 @@ public async Task TestQueryAsync_2() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.Equal( t => t.Code, code ).QueryAsync(); + var result = await _stateManager.Equal( t => t.Code, code ).QueryAsync(); Assert.Single( result ); Assert.Contains( result, t => t.Code == code ); } @@ -137,14 +158,14 @@ public async Task TestQueryAsync_3() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.In( "code", code, code2 ).QueryAsync(); + var result = await _stateManager.In( "code", code, code2 ).QueryAsync(); Assert.Equal( 2, result.Count ); Assert.Contains( result, t => t.Code == code ); Assert.Contains( result, t => t.Code == code2 ); @@ -161,14 +182,14 @@ public async Task TestQueryAsync_4() { //添加数据1 var dto = new CustomerDto2 { Code = code }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code2 }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.In( t => t.Code, code, code2 ).QueryAsync(); + var result = await _stateManager.In( t => t.Code, code, code2 ).QueryAsync(); Assert.Equal( 2, result.Count ); Assert.Contains( result, t => t.Code == code ); Assert.Contains( result, t => t.Code == code2 ); @@ -184,14 +205,14 @@ public async Task TestQueryAsync_OrderBy() { //添加数据1 var dto = new CustomerDto2 { Code = code, Name = "b" }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code, Name = "a" }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.OrderBy( t => t.Name ).Equal( "code", code ).QueryAsync(); + var result = await _stateManager.OrderBy( t => t.Name ).Equal( "code", code ).QueryAsync(); Assert.Equal( "a", result[0].Name ); Assert.Equal( "b", result[1].Name ); } @@ -206,14 +227,14 @@ public async Task TestQueryAsync_OrderByDescending() { //添加数据1 var dto = new CustomerDto2 { Code = code, Name = "a" }; - await _stateManage.SaveAsync( dto ); + await _stateManager.SaveAsync( dto ); //添加数据2 var dto2 = new CustomerDto2 { Code = code, Name = "b" }; - await _stateManage.SaveAsync( dto2 ); + await _stateManager.SaveAsync( dto2 ); //获取数据并验证 - var result = await _stateManage.OrderByDescending( t => t.Name ).Equal( "code", code ).QueryAsync(); + var result = await _stateManager.OrderByDescending( t => t.Name ).Equal( "code", code ).QueryAsync(); Assert.Equal( "b", result[0].Name ); Assert.Equal( "a", result[1].Name ); } @@ -231,7 +252,7 @@ public async Task TestPageQueryAsync() { var code = $"TestPageQueryAsync_{Id.Create()}"; //添加数据 - await _stateManage.SaveAsync( new[] { + await _stateManager.SaveAsync( new[] { new CustomerDto2 { Code = code, Name = "a" }, new CustomerDto2 { Code = code, Name = "b" }, new CustomerDto2 { Code = code, Name = "c" }, @@ -241,27 +262,27 @@ await _stateManage.SaveAsync( new[] { //分页查询第1页 var query = new Pager( 1, 2, "name desc" ); - var result = await _stateManage.Equal( "code", code ).PageQueryAsync( query ); + var result = await _stateManager.Equal( "code", code ).PageQueryAsync( query ); Assert.Equal( 2, result.Data.Count ); Assert.Equal( "e", result.Data[0].Name ); Assert.Equal( "d", result.Data[1].Name ); //分页查询第2页 query = new Pager( 2, 2, "name desc" ); - result = await _stateManage.Equal( "code", code ).PageQueryAsync( query ); + result = await _stateManager.Equal( "code", code ).PageQueryAsync( query ); Assert.Equal( 2, result.Data.Count ); Assert.Equal( "c", result.Data[0].Name ); Assert.Equal( "b", result.Data[1].Name ); //分页查询第3页 query = new Pager( 3, 2, "name desc" ); - result = await _stateManage.Equal( "code", code ).PageQueryAsync( query ); + result = await _stateManager.Equal( "code", code ).PageQueryAsync( query ); Assert.Single( result.Data ); Assert.Equal( "a", result.Data[0].Name ); //分页查询第4页 query = new Pager( 4, 2, "name desc" ); - result = await _stateManage.Equal( "code", code ).PageQueryAsync( query ); + result = await _stateManager.Equal( "code", code ).PageQueryAsync( query ); Assert.Empty( result.Data ); } diff --git a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.cs b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.cs similarity index 62% rename from test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.cs rename to test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.cs index 9dd488329..005f2c124 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManageTest.cs +++ b/test/Util.Microservices.Dapr.Tests.Integration/StateManagements/StateManagerTest.cs @@ -6,21 +6,21 @@ namespace Util.Microservices.Dapr.Tests.StateManagements; /// 状态管理测试 /// [Collection( "Global" )] -public partial class StateManageTest { +public partial class StateManagerTest { /// /// 状态管理操作 /// - private readonly IStateManage _stateManage; + private readonly IStateManager _stateManager; /// /// 日志 /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// 测试初始化 /// - public StateManageTest( IStateManage stateManage, ILogger logger ) { - _stateManage = stateManage; + public StateManagerTest( IStateManager stateManager, ILogger logger ) { + _stateManager = stateManager; _logger = logger; } @@ -34,10 +34,10 @@ public async Task Test_1() { var dto = new CustomerDto { Code = "123" }; //添加数据 - await _stateManage.AddAsync( key, dto ); + await _stateManager.AddAsync( key, dto ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Equal( "123", result.Code ); } @@ -51,18 +51,18 @@ public async Task Test_2() { var dto = new CustomerDto { Code = "123" }; //添加数据 - await _stateManage.AddAsync( key, dto ); + await _stateManager.AddAsync( key, dto ); //获取数据 - var eTagData = await _stateManage.GetStateAndETagAsync( key ); + var eTagData = await _stateManager.GetStateAndETagAsync( key ); Assert.Equal( "123", eTagData.value.Code ); //修改数据 dto.Code = "456"; - var isUpdate = await _stateManage.UpdateAsync( key, dto, eTagData.etag ); + var isUpdate = await _stateManager.UpdateAsync( key, dto, eTagData.etag ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.True( isUpdate ); Assert.Equal( "456", result.Code ); } @@ -77,11 +77,11 @@ public async Task Test_3() { var dto = new CustomerDto { Code = "123" }; //添加数据 - await _stateManage.AddAsync( key, dto ); + await _stateManager.AddAsync( key, dto ); //修改数据 dto.Code = "456"; - var isUpdate = await _stateManage.UpdateAsync( key, dto, "" ); + var isUpdate = await _stateManager.UpdateAsync( key, dto, "" ); Assert.False( isUpdate ); } @@ -95,13 +95,13 @@ public async Task Test_4() { var dto = new CustomerDto { Code = "123" }; //添加数据 - await _stateManage.AddAsync( key, dto ); + await _stateManager.AddAsync( key, dto ); //删除 - await _stateManage.RemoveAsync( key ); + await _stateManager.RemoveAsync( key ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Null( result ); } @@ -114,20 +114,20 @@ public async Task Test_5() { var dto = new CustomerDto2 { Code = "123" }; //添加数据 - var key = await _stateManage.SaveAsync( dto ); + var key = await _stateManager.SaveAsync( dto ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Equal( "123", result.Code ); Assert.False( result.ETag.IsEmpty() ); _logger.LogInformation( $"Test_5.1.ETag:{result.ETag}" ); //修改数据 result.Code = "456"; - await _stateManage.SaveAsync( result ); + await _stateManager.SaveAsync( result ); //获取数据并验证 - result = await _stateManage.GetAsync( key ); + result = await _stateManager.GetAsync( key ); Assert.Equal( "456", result.Code ); _logger.LogInformation( $"Test_5.2.ETag:{result.ETag}" ); } @@ -142,19 +142,19 @@ public async Task Test_6() { var dto = new CustomerDto2 { Code = "123" }; //添加数据 - await _stateManage.SaveAsync( dto, key: key ); + await _stateManager.SaveAsync( dto, key: key ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Equal( "123", result.Code ); Assert.False( result.ETag.IsEmpty() ); //修改数据 result.Code = "456"; - await _stateManage.SaveAsync( result, key: key ); + await _stateManager.SaveAsync( result, key: key ); //获取数据并验证 - result = await _stateManage.GetAsync( key ); + result = await _stateManager.GetAsync( key ); Assert.Equal( "456", result.Code ); } @@ -166,37 +166,37 @@ public async Task Test_7() { //添加数据1 var key_1 = $"key_{Id.Create()}"; var dto_1 = new CustomerDto { Code = "1" }; - await _stateManage.AddAsync( key_1, dto_1 ); + await _stateManager.AddAsync( key_1, dto_1 ); //添加数据2 var dto_2 = new CustomerDto2 { Code = "2" }; - var key_2 = await _stateManage.SaveAsync( dto_2 ); + var key_2 = await _stateManager.SaveAsync( dto_2 ); //开始事务 - _stateManage.BeginTransaction(); + _stateManager.BeginTransaction(); //添加数据3 var key_3 = $"key_{Id.Create()}"; var dto_3 = new CustomerDto { Code = "3" }; - await _stateManage.AddAsync( key_3, dto_3 ); + await _stateManager.AddAsync( key_3, dto_3 ); //修改数据1 - var result1 = await _stateManage.GetStateAndETagAsync( key_1 ); + var result1 = await _stateManager.GetStateAndETagAsync( key_1 ); result1.value.Code = "4"; - await _stateManage.UpdateAsync( key_1, result1.value, result1.etag ); + await _stateManager.UpdateAsync( key_1, result1.value, result1.etag ); //删除数据2 - await _stateManage.RemoveAsync( key_2 ); + await _stateManager.RemoveAsync( key_2 ); //提交事务 - await _stateManage.CommitAsync(); + await _stateManager.CommitAsync(); //获取数据并验证 - var result = await _stateManage.GetAsync( key_1 ); + var result = await _stateManager.GetAsync( key_1 ); Assert.Equal( "4", result.Code ); - result = await _stateManage.GetAsync( key_2 ); + result = await _stateManager.GetAsync( key_2 ); Assert.Null( result ); - result = await _stateManage.GetAsync( key_3 ); + result = await _stateManager.GetAsync( key_3 ); Assert.Equal( "3", result.Code ); } @@ -206,15 +206,15 @@ public async Task Test_7() { [Fact] public async Task Test_8() { //开始事务 - _stateManage.BeginTransaction(); + _stateManager.BeginTransaction(); //添加数据1 var key_1 = $"key_{Id.Create()}"; var dto_1 = new CustomerDto { Code = "1" }; - await _stateManage.AddAsync( key_1, dto_1 ); + await _stateManager.AddAsync( key_1, dto_1 ); //获取数据并验证 - var result = await _stateManage.GetAsync( key_1 ); + var result = await _stateManager.GetAsync( key_1 ); Assert.Null( result ); } @@ -226,18 +226,18 @@ public async Task Test_9() { //添加数据 var key_1 = $"key_{Id.Create()}"; var dto_1 = new CustomerDto { Code = "1" }; - await _stateManage.AddAsync( key_1, dto_1 ); + await _stateManager.AddAsync( key_1, dto_1 ); //开始事务 - _stateManage.BeginTransaction(); + _stateManager.BeginTransaction(); //修改数据 - var result = await _stateManage.GetStateAndETagAsync( key_1 ); + var result = await _stateManager.GetStateAndETagAsync( key_1 ); result.value.Code = "2"; - await _stateManage.UpdateAsync( key_1, result.value, result.etag ); + await _stateManager.UpdateAsync( key_1, result.value, result.etag ); //获取数据并验证 - dto_1 = await _stateManage.GetAsync( key_1 ); + dto_1 = await _stateManager.GetAsync( key_1 ); Assert.Equal( "1", dto_1.Code ); } @@ -249,16 +249,16 @@ public async Task Test_10() { //添加数据 var key_1 = $"key_{Id.Create()}"; var dto_1 = new CustomerDto { Code = "1" }; - await _stateManage.AddAsync( key_1, dto_1 ); + await _stateManager.AddAsync( key_1, dto_1 ); //开始事务 - _stateManage.BeginTransaction(); + _stateManager.BeginTransaction(); //修改数据 - await _stateManage.RemoveAsync( key_1 ); + await _stateManager.RemoveAsync( key_1 ); //获取数据并验证 - dto_1 = await _stateManage.GetAsync( key_1 ); + dto_1 = await _stateManager.GetAsync( key_1 ); Assert.Equal( "1", dto_1.Code ); } @@ -268,14 +268,14 @@ public async Task Test_10() { [Fact] public async Task Test_11() { //开始事务 - _stateManage.BeginTransaction(); + _stateManager.BeginTransaction(); //添加数据 var dto = new CustomerDto2 { Code = "1" }; - var key = await _stateManage.SaveAsync( dto ); + var key = await _stateManager.SaveAsync( dto ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Null( result ); } @@ -289,13 +289,13 @@ public async Task Test_12() { var dto = new CustomerDto2 { Code = "123" }; //添加数据 - await _stateManage.AddAsync( key, dto ); + await _stateManager.AddAsync( key, dto ); //删除 - await _stateManage.RemoveByIdAsync( dto.Id ); + await _stateManager.RemoveByIdAsync( dto.Id ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Null( result ); } @@ -308,10 +308,10 @@ public async Task Test_13() { var key = $"key_{Id.Create()}"; //添加数据 - await _stateManage.AddAsync( key, 1 ); + await _stateManager.AddAsync( key, 1 ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Equal( 1, result ); } @@ -324,10 +324,10 @@ public async Task Test_14() { var key = $"key_{Id.Create()}"; //添加数据 - await _stateManage.AddAsync( key, "a" ); + await _stateManager.AddAsync( key, "a" ); //获取数据并验证 - var result = await _stateManage.GetAsync( key ); + var result = await _stateManager.GetAsync( key ); Assert.Equal( "a", result ); } } \ No newline at end of file diff --git a/test/Util.Microservices.Dapr.Tests.Integration/Util.Microservices.Dapr.Tests.Integration.csproj b/test/Util.Microservices.Dapr.Tests.Integration/Util.Microservices.Dapr.Tests.Integration.csproj index a8d0d627d..1a1b09ba4 100644 --- a/test/Util.Microservices.Dapr.Tests.Integration/Util.Microservices.Dapr.Tests.Integration.csproj +++ b/test/Util.Microservices.Dapr.Tests.Integration/Util.Microservices.Dapr.Tests.Integration.csproj @@ -27,10 +27,10 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Microservices.Dapr.Tests/Util.Microservices.Dapr.Tests.csproj b/test/Util.Microservices.Dapr.Tests/Util.Microservices.Dapr.Tests.csproj index d1c082aba..aecc4c508 100644 --- a/test/Util.Microservices.Dapr.Tests/Util.Microservices.Dapr.Tests.csproj +++ b/test/Util.Microservices.Dapr.Tests/Util.Microservices.Dapr.Tests.csproj @@ -7,14 +7,14 @@ - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Util.Microservices.Dapr.WebApiSample/Controllers/IntegrationEventController.cs b/test/Util.Microservices.Dapr.WebApiSample/Controllers/IntegrationEventController.cs index eb923b49f..d8c38e875 100644 --- a/test/Util.Microservices.Dapr.WebApiSample/Controllers/IntegrationEventController.cs +++ b/test/Util.Microservices.Dapr.WebApiSample/Controllers/IntegrationEventController.cs @@ -10,7 +10,7 @@ public class IntegrationEventController : IntegrationEventControllerBase { /// /// 状态管理操作 /// - private readonly IStateManage _stateManage; + private readonly IStateManager _stateManager; /// /// 日志操作 /// @@ -19,8 +19,8 @@ public class IntegrationEventController : IntegrationEventControllerBase { /// /// 初始化集成事件控制器 /// - public IntegrationEventController( IStateManage stateManage, ILogger log ) { - _stateManage = stateManage; + public IntegrationEventController( IStateManager stateManager, ILogger log ) { + _stateManager = stateManager; _log = log; } @@ -31,7 +31,7 @@ public IntegrationEventController( IStateManage stateManage, ILogger Test1Async( TestEvent @event ) { _log.LogInformation( "========================================================WebApi_Test1:{@TestEvent}", @event ); - await _stateManage.StoreName( "event-state-store" ).AddAsync( $"webapi_{@event.Code}", @event ); + await _stateManager.StoreName( "event-state-store" ).AddAsync( $"webapi_{@event.Code}", @event ); return Success(); } diff --git a/test/Util.Microservices.Polly.Tests.Integration/Util.Microservices.Polly.Tests.Integration.csproj b/test/Util.Microservices.Polly.Tests.Integration/Util.Microservices.Polly.Tests.Integration.csproj index 08aa31d91..20263074a 100644 --- a/test/Util.Microservices.Polly.Tests.Integration/Util.Microservices.Polly.Tests.Integration.csproj +++ b/test/Util.Microservices.Polly.Tests.Integration/Util.Microservices.Polly.Tests.Integration.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/test/Util.ObjectMapping.AutoMapper.Tests/Util.ObjectMapping.AutoMapper.Tests.csproj b/test/Util.ObjectMapping.AutoMapper.Tests/Util.ObjectMapping.AutoMapper.Tests.csproj index f75a7ff85..2a0cdf8be 100644 --- a/test/Util.ObjectMapping.AutoMapper.Tests/Util.ObjectMapping.AutoMapper.Tests.csproj +++ b/test/Util.ObjectMapping.AutoMapper.Tests/Util.ObjectMapping.AutoMapper.Tests.csproj @@ -6,9 +6,9 @@ - + - + diff --git a/test/Util.Scheduling.Hangfire.Tests.Integration/Util.Scheduling.Hangfire.Tests.Integration.csproj b/test/Util.Scheduling.Hangfire.Tests.Integration/Util.Scheduling.Hangfire.Tests.Integration.csproj index 251451806..a4770e3a7 100644 --- a/test/Util.Scheduling.Hangfire.Tests.Integration/Util.Scheduling.Hangfire.Tests.Integration.csproj +++ b/test/Util.Scheduling.Hangfire.Tests.Integration/Util.Scheduling.Hangfire.Tests.Integration.csproj @@ -8,13 +8,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Scheduling.Quartz.Tests.Integration/Util.Scheduling.Quartz.Tests.Integration.csproj b/test/Util.Scheduling.Quartz.Tests.Integration/Util.Scheduling.Quartz.Tests.Integration.csproj index 78add02a2..318a904a4 100644 --- a/test/Util.Scheduling.Quartz.Tests.Integration/Util.Scheduling.Quartz.Tests.Integration.csproj +++ b/test/Util.Scheduling.Quartz.Tests.Integration/Util.Scheduling.Quartz.Tests.Integration.csproj @@ -22,13 +22,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Security.Tests/Util.Security.Tests.csproj b/test/Util.Security.Tests/Util.Security.Tests.csproj index 05ba08914..19d8ef116 100644 --- a/test/Util.Security.Tests/Util.Security.Tests.csproj +++ b/test/Util.Security.Tests/Util.Security.Tests.csproj @@ -6,7 +6,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Templates.Handlebars.Tests.Integration/Util.Templates.Handlebars.Tests.Integration.csproj b/test/Util.Templates.Handlebars.Tests.Integration/Util.Templates.Handlebars.Tests.Integration.csproj index 681c994b5..124f84872 100644 --- a/test/Util.Templates.Handlebars.Tests.Integration/Util.Templates.Handlebars.Tests.Integration.csproj +++ b/test/Util.Templates.Handlebars.Tests.Integration/Util.Templates.Handlebars.Tests.Integration.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Templates.Razor.Tests.Integration/Util.Templates.Razor.Tests.Integration.csproj b/test/Util.Templates.Razor.Tests.Integration/Util.Templates.Razor.Tests.Integration.csproj index b65c1dda9..97e157340 100644 --- a/test/Util.Templates.Razor.Tests.Integration/Util.Templates.Razor.Tests.Integration.csproj +++ b/test/Util.Templates.Razor.Tests.Integration/Util.Templates.Razor.Tests.Integration.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Tenants.Tests.Integration/Controllers/TestController.cs b/test/Util.Tenants.Tests.Integration/Controllers/TestController.cs new file mode 100644 index 000000000..027dc4a4a --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Controllers/TestController.cs @@ -0,0 +1,29 @@ +namespace Util.Tenants.Tests.Controllers; + +/// +/// 测试控制器 +/// +[Route( "api/test" )] +public class TestController : ControllerBase { + /// + /// 租户管理器 + /// + private readonly ITenantManager _tenantManager; + + /// + /// 初始化测试控制器 + /// + /// 租户管理器 + public TestController( ITenantManager tenantManager ) { + _tenantManager = tenantManager; + } + + /// + /// 获取当前租户标识 + /// + [HttpGet] + public IActionResult GetTenantIdAsync() { + var result = _tenantManager.GetTenantId(); + return new ContentResult { Content = result }; + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Properties/launchSettings.json b/test/Util.Tenants.Tests.Integration/Properties/launchSettings.json new file mode 100644 index 000000000..03a57330c --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Util.Tenants.Tests.Integration": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:39350;http://localhost:39351" + } + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Resolvers/CookieTenantResolverTest.cs b/test/Util.Tenants.Tests.Integration/Resolvers/CookieTenantResolverTest.cs new file mode 100644 index 000000000..cd68482ba --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Resolvers/CookieTenantResolverTest.cs @@ -0,0 +1,34 @@ +namespace Util.Tenants.Tests.Resolvers; + +/// +/// Cookie租户解析器测试 +/// +public class CookieTenantResolverTest { + /// + /// Http客户端 + /// + private readonly IHttpClient _client; + /// + /// 租户配置 + /// + private readonly TenantOptions _options; + + /// + /// 测试初始化 + /// + public CookieTenantResolverTest( IHttpClient client, IOptions options ) { + _client = client; + _options = options.Value; + } + + /// + /// 测试解析租户标识 + /// + [Fact] + public async Task TestResolveAsync() { + var result = await _client.Get( "/api/test" ) + .Cookie( _options.TenantKey,"a" ) + .GetResultAsync(); + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Resolvers/DomainTenantResolverTest.cs b/test/Util.Tenants.Tests.Integration/Resolvers/DomainTenantResolverTest.cs new file mode 100644 index 000000000..985410d63 --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Resolvers/DomainTenantResolverTest.cs @@ -0,0 +1,28 @@ +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 域名租户解析器测试 +/// +public class DomainTenantResolverTest { + /// + /// Http客户端 + /// + private readonly IHttpClient _client; + + /// + /// 测试初始化 + /// + public DomainTenantResolverTest( IHttpClient client ) { + _client = client; + } + + /// + /// 测试解析租户标识 + /// + [Fact] + public async Task TestResolveAsync() { + var result = await _client.Get( "http://a.test.com/api/test" ) + .GetResultAsync(); + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Resolvers/HeaderTenantResolverTest.cs b/test/Util.Tenants.Tests.Integration/Resolvers/HeaderTenantResolverTest.cs new file mode 100644 index 000000000..1f5e52453 --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Resolvers/HeaderTenantResolverTest.cs @@ -0,0 +1,34 @@ +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 请求头租户解析器测试 +/// +public class HeaderTenantResolverTest { + /// + /// Http客户端 + /// + private readonly IHttpClient _client; + /// + /// 租户配置 + /// + private readonly TenantOptions _options; + + /// + /// 测试初始化 + /// + public HeaderTenantResolverTest( IHttpClient client, IOptions options ) { + _client = client; + _options = options.Value; + } + + /// + /// 测试解析租户标识 + /// + [Fact] + public async Task TestResolveAsync() { + var result = await _client.Get( "/api/test" ) + .Header( _options.TenantKey, "a" ) + .GetResultAsync(); + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Resolvers/QueryStringTenantResolverTest.cs b/test/Util.Tenants.Tests.Integration/Resolvers/QueryStringTenantResolverTest.cs new file mode 100644 index 000000000..7579230aa --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Resolvers/QueryStringTenantResolverTest.cs @@ -0,0 +1,33 @@ +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 查询字符串租户解析器测试 +/// +public class QueryStringTenantResolverTest { + /// + /// Http客户端 + /// + private readonly IHttpClient _client; + /// + /// 租户配置 + /// + private readonly TenantOptions _options; + + /// + /// 测试初始化 + /// + public QueryStringTenantResolverTest( IHttpClient client, IOptions options ) { + _client = client; + _options = options.Value; + } + + /// + /// 测试解析租户标识 + /// + [Fact] + public async Task TestResolveAsync() { + var result = await _client.Get( $"/api/test?{_options.TenantKey}=a" ) + .GetResultAsync(); + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Startup.cs b/test/Util.Tenants.Tests.Integration/Startup.cs new file mode 100644 index 000000000..87542c0bb --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Startup.cs @@ -0,0 +1,43 @@ +using Util.Helpers; + +namespace Util.Tenants.Tests; + +/// +/// +/// +public class Startup { + /// + /// + /// + public void ConfigureHost( IHostBuilder hostBuilder ) { + Environment.SetDevelopment(); + hostBuilder.ConfigureDefaults( null ) + .ConfigureWebHostDefaults( webHostBuilder => { + webHostBuilder.UseTestServer() + .Configure( t => { + t.UseTenant(); + t.UseRouting(); + t.UseEndpoints( endpoints => { + endpoints.MapControllers(); + } ); + } ); + } ) + .AsBuild() + .AddAop() + .AddTenant() + .AddUtil(); + } + + /// + /// ÷ + /// + public void ConfigureServices( IServiceCollection services ) { + services.AddLogging( logBuilder => logBuilder.AddXunitOutput() ); + services.AddControllers(); + services.AddTransient( t => { + var client = new HttpClientService(); + client.SetHttpClient( t.GetService().GetTestClient() ); + return client; + } ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Usings.cs b/test/Util.Tenants.Tests.Integration/Usings.cs new file mode 100644 index 000000000..a785370d5 --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Usings.cs @@ -0,0 +1,12 @@ +global using System.Threading.Tasks; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Hosting; +global using Microsoft.AspNetCore.TestHost; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Options; +global using Microsoft.AspNetCore.Mvc; +global using Xunit; +global using Xunit.DependencyInjection.Logging; +global using Util.Http; +global using Util.Aop; \ No newline at end of file diff --git a/test/Util.Tenants.Tests.Integration/Util.Tenants.Tests.Integration.csproj b/test/Util.Tenants.Tests.Integration/Util.Tenants.Tests.Integration.csproj new file mode 100644 index 000000000..6320033a0 --- /dev/null +++ b/test/Util.Tenants.Tests.Integration/Util.Tenants.Tests.Integration.csproj @@ -0,0 +1,32 @@ + + + + $(NetTargetFramework) + false + Util.Tenants.Tests + Util.Tenants.Tests.Startup + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Managements/TenantManagerTest.cs b/test/Util.Tenants.Tests/Managements/TenantManagerTest.cs new file mode 100644 index 000000000..0dc457eaa --- /dev/null +++ b/test/Util.Tenants.Tests/Managements/TenantManagerTest.cs @@ -0,0 +1,281 @@ +using Util.Tenants.Managements; +using Util.Tenants.Tests.Samples; +using ISession = Util.Sessions.ISession; + +namespace Util.Tenants.Tests.Managements; + +/// +/// 租户管理器测试 +/// +public class TenantManagerTest { + + #region 测试初始化 + + /// + /// 模拟租户存储器 + /// + private readonly Mock _mockStore; + /// + /// 模拟查看租户管理器 + /// + private readonly Mock _mockView; + /// + /// 模拟切换租户管理器 + /// + private readonly Mock _mockSwitch; + /// + /// 模拟配置 + /// + private readonly Mock> _mockOptions; + /// + /// 租户管理器 + /// + private TenantManager _tenantManager; + + /// + /// 测试初始化 + /// + public TenantManagerTest() { + _mockStore = new Mock(); + _mockView = new Mock(); + _mockSwitch = new Mock(); + _mockOptions = new Mock>(); + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { IsEnabled = true } ); + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, GetTestSession(), _mockOptions.Object ); + } + + /// + /// 获取测试用户会话 + /// + private ISession GetTestSession() { + return new TestSession( false, null, null ); + } + + #endregion + + #region IsHost + + /// + /// 测试是否平台供应商 + /// + [Fact] + public void TestIsHost_1() { + //设置 + var tenantId = "t"; + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { DefaultTenantId = tenantId } ); + TenantManager.CurrentTenantId = tenantId; + + //执行 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //验证 + Assert.True( _tenantManager.IsHost() ); + } + + /// + /// 测试是否平台供应商 - 未登录 + /// + [Fact] + public void TestIsHost_2() { + //设置 + var tenantId = "t"; + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { DefaultTenantId = tenantId } ); + TenantManager.CurrentTenantId = tenantId; + + //执行 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( false, "u", tenantId ), _mockOptions.Object ); + + //验证 + Assert.False( _tenantManager.IsHost() ); + } + + /// + /// 测试是否平台供应商 - 用户名为空 + /// + [Fact] + public void TestIsHost_3() { + //设置 + var tenantId = "t"; + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { DefaultTenantId = tenantId } ); + TenantManager.CurrentTenantId = tenantId; + + //执行 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "", tenantId ), _mockOptions.Object ); + + //验证 + Assert.False( _tenantManager.IsHost() ); + } + + /// + /// 测试是否平台供应商 - 用户会话中的租户标识与当前租户不同 + /// + [Fact] + public void TestIsHost_4() { + //设置 + var tenantId = "t"; + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { DefaultTenantId = tenantId } ); + TenantManager.CurrentTenantId = "t2"; + + //执行 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //验证 + Assert.False( _tenantManager.IsHost() ); + } + + /// + /// 测试是否平台供应商 - 当前租户标识与默认租户不同 + /// + [Fact] + public void TestIsHost_5() { + //设置 + var tenantId = "t"; + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { DefaultTenantId = "" } ); + TenantManager.CurrentTenantId = tenantId; + + //执行 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //验证 + Assert.False( _tenantManager.IsHost() ); + } + + #endregion + + #region GetTenantId + + /// + /// 测试获取当前租户标识 - 普通租户,返回租户标识 + /// + [Fact] + public void TestGetTenantId_1() { + //设置当前租户标识 + TenantManager.CurrentTenantId = "a"; + + //执行 + var result = _tenantManager.GetTenantId(); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试获取当前租户标识 - 平台供应商,未切换租户,返回平台供应商租户标识 + /// + [Fact] + public void TestGetTenantId_2() { + //变量定义 + var tenantId = "t"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //设置 + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { IsEnabled = true, DefaultTenantId = tenantId } ); + + //重新初始化 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //执行 + var result = _tenantManager.GetTenantId(); + + //验证 + Assert.Equal( tenantId, result ); + } + + /// + /// 测试获取当前租户标识 - 平台供应商,切换租户,返回切换的租户标识 + /// + [Fact] + public void TestGetTenantId_3() { + //变量定义 + var tenantId = "t"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //设置 + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { IsEnabled = true, DefaultTenantId = tenantId } ); + _mockSwitch.Setup( t => t.GetSwitchTenantId() ).Returns( "t2" ); + + //重新初始化 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //执行 + var result = _tenantManager.GetTenantId(); + + //验证 + Assert.Equal( "t2", result ); + } + + #endregion + + #region IsDisableTenantFilter + + /// + /// 测试是否禁用租户过滤器 - 普通租户,不禁用 + /// + [Fact] + public void TestIsDisableTenantFilter_1() { + //设置 + _mockView.Setup( t => t.IsDisableTenantFilter() ).Returns( true ); + + //执行 + var result = _tenantManager.IsDisableTenantFilter(); + + //验证 + Assert.False( result ); + } + + /// + /// 测试是否禁用租户过滤器 - 平台供应商,查看租户管理器设置为true + /// + [Fact] + public void TestIsDisableTenantFilter_2() { + //变量定义 + var tenantId = "t"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //设置 + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { IsEnabled = true, DefaultTenantId = tenantId } ); + _mockView.Setup( t => t.IsDisableTenantFilter() ).Returns( true ); + + //重新初始化 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //执行 + var result = _tenantManager.IsDisableTenantFilter(); + + //验证 + Assert.True( result ); + } + + /// + /// 测试是否禁用租户过滤器 - 平台供应商,查看租户管理器设置为false + /// + [Fact] + public void TestIsDisableTenantFilter_3() { + //变量定义 + var tenantId = "t"; + + //设置当前租户标识 + TenantManager.CurrentTenantId = tenantId; + + //设置 + _mockOptions.Setup( t => t.Value ).Returns( new TenantOptions { IsEnabled = true, DefaultTenantId = tenantId } ); + _mockView.Setup( t => t.IsDisableTenantFilter() ).Returns( false ); + + //重新初始化 + _tenantManager = new TenantManager( _mockStore.Object, _mockView.Object, _mockSwitch.Object, new TestSession( true, "u", tenantId ), _mockOptions.Object ); + + //执行 + var result = _tenantManager.IsDisableTenantFilter(); + + //验证 + Assert.False( result ); + } + + #endregion +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/CookieTenantResolverTest.cs b/test/Util.Tenants.Tests/Resolvers/CookieTenantResolverTest.cs new file mode 100644 index 000000000..6d9315f2f --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/CookieTenantResolverTest.cs @@ -0,0 +1,70 @@ +using Util.Helpers; +using Util.Tenants.Resolvers; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// Cookie租户解析器测试 +/// +public class CookieTenantResolverTest { + /// + /// Cookie租户解析器 + /// + private readonly CookieTenantResolver _resolver; + /// + /// 模拟Http上下文 + /// + private readonly Mock _mockHttpContext; + + /// + /// 测试初始化 + /// + public CookieTenantResolverTest() { + _resolver = new CookieTenantResolver(); + _mockHttpContext = new Mock(); + } + + /// + /// 测试解析租户标识 - 默认租户键名 + /// + [Fact] + public async Task TestResolveAsync_1() { + //设置Cookie + var mockCookies = new Mock(); + mockCookies.Setup( t => t[TenantOptions.DefaultTenantKey] ).Returns( "a" ); + _mockHttpContext.Setup( t => t.Request.Cookies ).Returns( mockCookies.Object ); + + //设置租户配置 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.IsEnabled = true ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定租户键名 + /// + [Fact] + public async Task TestResolveAsync_2() { + //设置Cookie + var mockCookies = new Mock(); + mockCookies.Setup( t => t["key"] ).Returns( "a" ); + _mockHttpContext.Setup( t => t.Request.Cookies ).Returns( mockCookies.Object ); + + //设置租户键名 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.TenantKey = "key" ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/DefaultTenantResolverTest.cs b/test/Util.Tenants.Tests/Resolvers/DefaultTenantResolverTest.cs new file mode 100644 index 000000000..d24ef49a4 --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/DefaultTenantResolverTest.cs @@ -0,0 +1,130 @@ +using Util.Helpers; +using Util.Tenants.Resolvers; +using Util.Tenants.Tests.Samples; +using ISession = Util.Sessions.ISession; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 默认租户解析器测试 +/// +public class DefaultTenantResolverTest { + /// + /// 默认租户解析器 + /// + private readonly DefaultTenantResolver _resolver; + + /// + /// 测试初始化 + /// + public DefaultTenantResolverTest() { + _resolver = new DefaultTenantResolver( GetMockOptions().Object ); + } + + /// + /// 获取模拟配置 + /// + private Mock> GetMockOptions( bool isEnabled = true ) { + var mockOptions = new Mock>(); + var options = new TenantOptions { + IsEnabled = isEnabled + }; + options.Resolvers.Clear(); + options.Resolvers.Add( new TestTenantResolver() ); + options.Resolvers.Add( new Test2TenantResolver() ); + options.Resolvers.Add( new Test3TenantResolver() ); + mockOptions.Setup( t => t.Value ).Returns( options ); + return mockOptions; + } + + /// + /// 测试解析租户标识 - Test3TenantResolver优先级最高,但返回空字符串,Test2TenantResolver的优先级次之,TestTenantResolver不执行 + /// + [Fact] + public async Task TestResolveAsync_1() { + //创建模拟用户会话 + var mockSession = new Mock(); + mockSession.Setup( t => t.IsAuthenticated ).Returns( false ); + mockSession.Setup( t => t.TenantId ).Returns( "a" ); + + //创建模拟Http上下文 + var container = Ioc.CreateContainer(); + var services = container.GetServices(); + services.AddTransient( _ => mockSession.Object ); + var mockHttpContext = new Mock(); + mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行并验证 + var result = await _resolver.ResolveAsync( mockHttpContext.Object ); + Assert.Equal( "Test2", result ); + } + + /// + /// 测试解析租户标识 - 未启用租户,返回空 + /// + [Fact] + public async Task TestResolveAsync_2() { + //禁用租户 + var resolver = new DefaultTenantResolver( GetMockOptions( false ).Object ); + + //创建模拟用户会话 + var mockSession = new Mock(); + mockSession.Setup( t => t.IsAuthenticated ).Returns( false ); + mockSession.Setup( t => t.TenantId ).Returns( "a" ); + + //创建模拟Http上下文 + var container = Ioc.CreateContainer(); + var services = container.GetServices(); + services.AddTransient( _ => mockSession.Object ); + var mockHttpContext = new Mock(); + mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行并验证 + var result = await resolver.ResolveAsync( mockHttpContext.Object ); + Assert.Null( result ); + } + + /// + /// 测试解析租户标识 - 用户已登录,返回用户租户标识 + /// + [Fact] + public async Task TestResolveAsync_3() { + //创建模拟用户会话 + var mockSession = new Mock(); + mockSession.Setup( t => t.IsAuthenticated ).Returns( true ); + mockSession.Setup( t => t.TenantId ).Returns( "a" ); + + //创建模拟Http上下文 + var container = Ioc.CreateContainer(); + var services = container.GetServices(); + services.AddTransient( _ => mockSession.Object ); + var mockHttpContext = new Mock(); + mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行并验证 + var result = await _resolver.ResolveAsync( mockHttpContext.Object ); + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 用户已登录,返回用户租户标识 - 租户标识为空 + /// + [Fact] + public async Task TestResolveAsync_4() { + //创建模拟用户会话 + var mockSession = new Mock(); + mockSession.Setup( t => t.IsAuthenticated ).Returns( true ); + mockSession.Setup( t => t.TenantId ).Returns( string.Empty ); + + //创建模拟Http上下文 + var container = Ioc.CreateContainer(); + var services = container.GetServices(); + services.AddTransient( _ => mockSession.Object ); + var mockHttpContext = new Mock(); + mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行并验证 + var result = await _resolver.ResolveAsync( mockHttpContext.Object ); + Assert.Equal( string.Empty, result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/DomainTenantResolverTest.cs b/test/Util.Tenants.Tests/Resolvers/DomainTenantResolverTest.cs new file mode 100644 index 000000000..512454c70 --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/DomainTenantResolverTest.cs @@ -0,0 +1,194 @@ +using Util.Helpers; +using Util.Tenants.Resolvers; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 域名租户解析器测试 +/// +public class DomainTenantResolverTest { + /// + /// 域名租户解析器 + /// + private DomainTenantResolver _resolver; + /// + /// 模拟Http上下文 + /// + private readonly Mock _mockHttpContext; + + /// + /// 测试初始化 + /// + public DomainTenantResolverTest() { + _resolver = new DomainTenantResolver(); + _mockHttpContext = new Mock(); + } + + /// + /// 测试解析租户标识 - 默认解析 - 2级域名 + /// + [Fact] + public async Task TestResolveAsync_1() { + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "http://a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 默认解析 - 顶级域名 + /// + [Fact] + public async Task TestResolveAsync_2() { + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "http://test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Null( result ); + } + + /// + /// 测试解析租户标识 - 默认解析 - 3级域名 + /// + [Fact] + public async Task TestResolveAsync_3() { + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://a.b.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定域名格式 + /// + [Fact] + public async Task TestResolveAsync_4() { + //传入域名格式 + _resolver = new DomainTenantResolver( "b.{0}.test.com" ); + + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://b.a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定域名格式 - http前缀 + /// + [Fact] + public async Task TestResolveAsync_5() { + //传入域名格式 + _resolver = new DomainTenantResolver( "http://b.{0}.test.com" ); + + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://b.a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定多个域名格式 - 逗号分隔 + /// + [Fact] + public async Task TestResolveAsync_6() { + //传入域名格式 + _resolver = new DomainTenantResolver( "b.{0}.test.com,{0}.test.com" ); + + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://b.a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定多个域名格式 - 分号分隔 + /// + [Fact] + public async Task TestResolveAsync_7() { + //传入域名格式 + _resolver = new DomainTenantResolver( "http://b.{0}.test.com;;https://{0}.test.com" ); + + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://b.a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 域名映射配置 + /// + [Fact] + public async Task TestResolveAsync_8() { + //传入域名格式 + var map = new Dictionary { + {"a.test.com","b"} + }; + _resolver = new DomainTenantResolver( map ); + + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://a.test.com" ) ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( Ioc.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "b", result ); + } + + /// + /// 测试解析租户标识 - IDomainTenantResolver 接口解析 + /// + [Fact] + public async Task TestResolveAsync_9() { + //设置主机名 + _mockHttpContext.Setup( t => t.Request.Host ).Returns( new HostString( "https://a.test.com" ) ); + + //设置IDomainTenantResolver + var mockResolver = new Mock(); + mockResolver.Setup( t => t.ResolveTenantIdAsync( "a.test.com" ) ).ReturnsAsync( "b" ); + var container = Ioc.CreateContainer(); + container.GetServices().AddTransient( _ => mockResolver.Object ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "b", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/HeaderTenantResolverTest.cs b/test/Util.Tenants.Tests/Resolvers/HeaderTenantResolverTest.cs new file mode 100644 index 000000000..543a05816 --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/HeaderTenantResolverTest.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.Primitives; +using Util.Helpers; +using Util.Tenants.Resolvers; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 请求头租户解析器测试 +/// +public class HeaderTenantResolverTest { + /// + /// 请求头租户解析器 + /// + private readonly HeaderTenantResolver _resolver; + /// + /// 模拟Http上下文 + /// + private readonly Mock _mockHttpContext; + + /// + /// 测试初始化 + /// + public HeaderTenantResolverTest() { + _resolver = new HeaderTenantResolver(); + _mockHttpContext = new Mock(); + } + + /// + /// 测试解析租户标识 - 默认租户键名 + /// + [Fact] + public async Task TestResolveAsync_1() { + //设置请求头 + var headers = new Dictionary { + { TenantOptions.DefaultTenantKey,"a" } + }; + _mockHttpContext.Setup( t => t.Request.Headers ).Returns( new HeaderDictionary( headers ) ); + + //设置租户配置 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.IsEnabled = true ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定租户键名 + /// + [Fact] + public async Task TestResolveAsync_2() { + //设置请求头 + var headers = new Dictionary { + { "key","a" } + }; + _mockHttpContext.Setup( t => t.Request.Headers ).Returns( new HeaderDictionary( headers ) ); + + //设置租户键名 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.TenantKey = "key" ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/QueryStringTenantResolverTest.cs b/test/Util.Tenants.Tests/Resolvers/QueryStringTenantResolverTest.cs new file mode 100644 index 000000000..fac22f8f0 --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/QueryStringTenantResolverTest.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.Primitives; +using Util.Helpers; +using Util.Tenants.Resolvers; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 查询字符串租户解析器测试 +/// +public class QueryStringTenantResolverTest { + /// + /// 查询字符串租户解析器 + /// + private readonly QueryStringTenantResolver _resolver; + /// + /// 模拟Http上下文 + /// + private readonly Mock _mockHttpContext; + + /// + /// 测试初始化 + /// + public QueryStringTenantResolverTest() { + _resolver = new QueryStringTenantResolver(); + _mockHttpContext = new Mock(); + } + + /// + /// 测试解析租户标识 - 默认租户键名 + /// + [Fact] + public async Task TestResolveAsync_1() { + //设置查询字符串 + var query = new Dictionary { + { TenantOptions.DefaultTenantKey,"a" } + }; + _mockHttpContext.Setup( t => t.Request.Query ).Returns( new QueryCollection( query ) ); + + //设置租户配置 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.IsEnabled = true ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } + + /// + /// 测试解析租户标识 - 指定租户键名 + /// + [Fact] + public async Task TestResolveAsync_2() { + //设置查询字符串 + var query = new Dictionary { + { "key","a" } + }; + _mockHttpContext.Setup( t => t.Request.Query ).Returns( new QueryCollection( query ) ); + + //设置租户键名 + var container = Ioc.CreateContainer(); + container.GetServices().Configure( t => t.TenantKey = "key" ); + _mockHttpContext.Setup( t => t.RequestServices ).Returns( container.GetServiceProvider() ); + + //执行 + var result = await _resolver.ResolveAsync( _mockHttpContext.Object ); + + //验证 + Assert.Equal( "a", result ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Resolvers/TenantResolverCollectionTest.cs b/test/Util.Tenants.Tests/Resolvers/TenantResolverCollectionTest.cs new file mode 100644 index 000000000..4dfc62138 --- /dev/null +++ b/test/Util.Tenants.Tests/Resolvers/TenantResolverCollectionTest.cs @@ -0,0 +1,86 @@ +using Util.Tenants.Tests.Samples; + +namespace Util.Tenants.Tests.Resolvers; + +/// +/// 租户解析器集合测试 +/// +public class TenantResolverCollectionTest { + /// + /// 租户解析器集合 + /// + private readonly TenantResolverCollection _resolvers; + + /// + /// 测试初始化 + /// + public TenantResolverCollectionTest() { + _resolvers = new TenantResolverCollection(); + } + + /// + /// 测试添加租户解析器 + /// + [Fact] + public void TestAddTenantResolver_1() { + _resolvers.Add( new TestTenantResolver() ); + var result = _resolvers.GetResolvers(); + Assert.Single( result ); + } + + /// + /// 测试添加租户解析器 - 重复检测 + /// + [Fact] + public void TestAddTenantResolver_2() { + _resolvers.Add( new TestTenantResolver() ); + _resolvers.Add( new TestTenantResolver() ); + var result = _resolvers.GetResolvers(); + Assert.Single( result ); + } + + /// + /// 测试添加租户解析器 - 指定键 + /// + [Fact] + public void TestAddTenantResolver_3() { + _resolvers.Add( "a", new TestTenantResolver() ); + _resolvers.Add( "b", new TestTenantResolver() ); + var result = _resolvers.GetResolvers(); + Assert.Equal( 2, result.Count ); + } + + /// + /// 测试添加租户解析器集合 + /// + [Fact] + public void TestAddTenantResolvers() { + _resolvers.Add( new List { new TestTenantResolver(), new Test2TenantResolver() } ); + var result = _resolvers.GetResolvers(); + Assert.Equal( 2, result.Count ); + } + + /// + /// 测试移除租户解析器 - 按类型移除 + /// + [Fact] + public void TestRemoveTenantResolver_1() { + _resolvers.Add( new TestTenantResolver() ); + _resolvers.Remove(); + var result = _resolvers.GetResolvers(); + Assert.Empty( result ); + } + + /// + /// 测试移除租户解析器 - 按键名移除 + /// + [Fact] + public void TestRemoveTenantResolver_2() { + _resolvers.Add( "a", new TestTenantResolver() ); + _resolvers.Add( "b", new Test2TenantResolver() ); + _resolvers.Remove( "a" ); + var result = _resolvers.GetResolvers(); + Assert.Single( result ); + Assert.Equal( typeof( Test2TenantResolver ), result[0].GetType() ); + } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Samples/TestSession.cs b/test/Util.Tenants.Tests/Samples/TestSession.cs new file mode 100644 index 000000000..2c3a6d3d7 --- /dev/null +++ b/test/Util.Tenants.Tests/Samples/TestSession.cs @@ -0,0 +1,22 @@ +using ISession = Util.Sessions.ISession; + +namespace Util.Tenants.Tests.Samples; + +/// +/// 测试用户会话 +/// +public class TestSession : ISession { + /// + /// 初始化测试用户会话 + /// + public TestSession( bool isAuthenticated, string userId, string tenantId ) { + IsAuthenticated = isAuthenticated; + UserId = userId; + TenantId = tenantId; + } + + public IServiceProvider ServiceProvider { get; } + public bool IsAuthenticated { get; } + public string UserId { get; } + public string TenantId { get; } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Samples/TestTenantResolver.cs b/test/Util.Tenants.Tests/Samples/TestTenantResolver.cs new file mode 100644 index 000000000..b8ae82c56 --- /dev/null +++ b/test/Util.Tenants.Tests/Samples/TestTenantResolver.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Http; + +namespace Util.Tenants.Tests.Samples; + +/// +/// 测试租户解析器 +/// +public class TestTenantResolver : ITenantResolver { + /// + /// 初始化测试租户解析器 + /// + public TestTenantResolver() { + Priority = 1; + } + + /// + public Task ResolveAsync( HttpContext context ) { + return Task.FromResult( "Test" ); + } + + /// + public int Priority { get; set; } +} + +/// +/// 测试租户解析器2 +/// +public class Test2TenantResolver : ITenantResolver { + /// + /// 初始化测试租户解析器 + /// + public Test2TenantResolver() { + Priority = 2; + } + + /// + public Task ResolveAsync( HttpContext context ) { + return Task.FromResult( "Test2" ); + } + + /// + public int Priority { get; set; } +} + +/// +/// 测试租户解析器3 +/// +public class Test3TenantResolver : ITenantResolver { + /// + /// 初始化测试租户解析器 + /// + public Test3TenantResolver() { + Priority = 3; + } + + /// + public Task ResolveAsync( HttpContext context ) { + return Task.FromResult( "" ); + } + + /// + public int Priority { get; set; } +} \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Usings.cs b/test/Util.Tenants.Tests/Usings.cs new file mode 100644 index 000000000..837246791 --- /dev/null +++ b/test/Util.Tenants.Tests/Usings.cs @@ -0,0 +1,12 @@ +global using System; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.AspNetCore.Http; +global using Moq; +global using Util.Security.Authorization; +global using Util.Sessions; +global using Xunit; \ No newline at end of file diff --git a/test/Util.Tenants.Tests/Util.Tenants.Tests.csproj b/test/Util.Tenants.Tests/Util.Tenants.Tests.csproj new file mode 100644 index 000000000..3a5d06b5b --- /dev/null +++ b/test/Util.Tenants.Tests/Util.Tenants.Tests.csproj @@ -0,0 +1,26 @@ + + + + $(NetTargetFramework) + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Util.TestShare.SqlServer/EntityTypeConfigurations/ProductConfiguration.cs b/test/Util.TestShare.SqlServer/EntityTypeConfigurations/ProductConfiguration.cs index a40ad246f..d847a4b06 100644 --- a/test/Util.TestShare.SqlServer/EntityTypeConfigurations/ProductConfiguration.cs +++ b/test/Util.TestShare.SqlServer/EntityTypeConfigurations/ProductConfiguration.cs @@ -23,7 +23,7 @@ public void Configure( EntityTypeBuilder builder ) { /// 配置表 /// private void ConfigTable( EntityTypeBuilder builder ) { - builder.ToTable( "Product", "Products" ).HasComment( "产品" ); + builder.ToTable( "Product", "Products",t => t.HasComment( "产品" ) ); } /// diff --git a/test/Util.TestShare.SqlServer/UnitOfWorks/SqlServerUnitOfWork.cs b/test/Util.TestShare.SqlServer/UnitOfWorks/SqlServerUnitOfWork.cs index 832b7e23f..b3b648f98 100644 --- a/test/Util.TestShare.SqlServer/UnitOfWorks/SqlServerUnitOfWork.cs +++ b/test/Util.TestShare.SqlServer/UnitOfWorks/SqlServerUnitOfWork.cs @@ -7,7 +7,7 @@ namespace Util.Tests.UnitOfWorks { /// /// SqlServer工作单元 /// - public class SqlServerUnitOfWork : SqlServerUnitOfWorkBase, ITestUnitOfWork { + public class SqlServerUnitOfWork : SqlServerUnitOfWorkBase, ITestUnitOfWork, ITestUnitOfWork2 { /// /// 初始化工作单元 /// diff --git a/test/Util.TestShare/EventHandlers/TestEventHandler.cs b/test/Util.TestShare/EventHandlers/TestEventHandler.cs new file mode 100644 index 000000000..ede474145 --- /dev/null +++ b/test/Util.TestShare/EventHandlers/TestEventHandler.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; +using Util.Events; +using Util.Exceptions; +using Util.Tests.Events; + +namespace Util.Tests.EventHandlers; + +/// +/// 测试事件2处理器 +/// +public class TestEvent2Handler : EventHandlerBase { + /// + /// 处理事件 + /// + public override async Task HandleAsync( TestEvent2 @event, CancellationToken cancellationToken ) { + await Task.CompletedTask; + throw new Warning( @event.Id.ToString() ); + } +} + +/// +/// 测试事件3处理器 +/// +public class TestEvent3Handler : EventHandlerBase { + /// + /// 处理事件 + /// + public override async Task HandleAsync( TestEvent3 @event, CancellationToken cancellationToken ) { + await Task.CompletedTask; + throw new Warning( @event.Id.ToString() ); + } +} \ No newline at end of file diff --git a/test/Util.TestShare/Events/TestEvent.cs b/test/Util.TestShare/Events/TestEvent.cs index c6d031078..8f8c39401 100644 --- a/test/Util.TestShare/Events/TestEvent.cs +++ b/test/Util.TestShare/Events/TestEvent.cs @@ -9,4 +9,18 @@ namespace Util.Tests.Events; public class TestEvent : IEvent { public Guid Id { get; set; } public string Name { get; set; } +} + +/// +/// 测试事件2 +/// +public class TestEvent2 : IEvent { + public Guid Id { get; set; } + public string Name { get; set; } +} + +/// +/// 测试事件3 - 集成事件 +/// +public record TestEvent3( Guid Id, string Name ) : IntegrationEvent { } \ No newline at end of file diff --git a/test/Util.TestShare/Fakes/ProductFakeService.cs b/test/Util.TestShare/Fakes/ProductFakeService.cs index 06e477b2f..c334737ff 100644 --- a/test/Util.TestShare/Fakes/ProductFakeService.cs +++ b/test/Util.TestShare/Fakes/ProductFakeService.cs @@ -23,12 +23,13 @@ public static Product GetProduct() { /// 行数 public static List GetProducts( int count ) { return new AutoFaker() - .RuleFor( t => t.Code, t => t.Random.String2( 1, 50 ) ) + .RuleFor( t => t.Code, t => t.Random.String2( 1, 20 ) ) .RuleFor( t => t.Name, t => t.Random.String2( 1, 500 ) ) .Ignore( t => t.CreationTime ) .Ignore( t => t.CreatorId ) .Ignore( t => t.LastModificationTime ) .Ignore( t => t.LastModifierId ) + .Ignore( t => t.TenantId ) .Ignore( t => t.TestProperty1 ) .RuleFor( t => t.IsDeleted, false ) .Generate( count ); @@ -50,7 +51,7 @@ public static List GetProductDtos( int count ) { .Configure( builder => builder .WithSkip( t => t.Id ) ) - .RuleFor( t => t.Code, t => t.Random.String2( 1, 50 ) ) + .RuleFor( t => t.Code, t => t.Random.String2( 1, 20 ) ) .RuleFor( t => t.Name, t => t.Random.String2( 1, 500 ) ) .Ignore( t => t.CreationTime ) .Ignore( t => t.CreatorId ) diff --git a/test/Util.TestShare/Infrastructure/TestSession.cs b/test/Util.TestShare/Infrastructure/TestSession.cs index 2299639fd..ae84fccb9 100644 --- a/test/Util.TestShare/Infrastructure/TestSession.cs +++ b/test/Util.TestShare/Infrastructure/TestSession.cs @@ -7,12 +7,18 @@ namespace Util.Tests.Infrastructure; /// 测试用户会话 /// public class TestSession : ISession { + /// + /// 服务提供器 + /// + public IServiceProvider ServiceProvider => null; + public TestSession() { UserId = TestUserId.ToString(); } public bool IsAuthenticated => true; public string UserId { get; set; } + public string TenantId { get; set; } /// /// 测试用户标识 diff --git a/test/Util.TestShare/Models/Product.cs b/test/Util.TestShare/Models/Product.cs index 945a71287..980e44ce7 100644 --- a/test/Util.TestShare/Models/Product.cs +++ b/test/Util.TestShare/Models/Product.cs @@ -7,14 +7,16 @@ using Util.Domain.Auditing; using Util.Domain.Entities; using Util.Domain.Extending; +using Util.Tenants; +using Util.Tests.Events; -namespace Util.Tests.Models; +namespace Util.Tests.Models; /// /// 产品 /// [Description( "产品" )] -public class Product : AggregateRoot, IDelete, IAudited, IExtraProperties { +public class Product : AggregateRoot, IDelete, ITenant, IAudited, IExtraProperties { /// /// 初始化产品 /// @@ -35,7 +37,6 @@ public Product( Guid id ) : base( id ) { /// 产品编码 /// [Description( "产品编码" )] - [MaxLength( 50 )] public string Code { get; set; } /// /// 产品名称 @@ -98,6 +99,11 @@ public Product( Guid id ) : base( id ) { /// [Description( "是否删除" )] public bool IsDeleted { get; set; } + /// + /// 租户标识 + /// + [Description( "租户标识" )] + public string TenantId { get; set; } /// /// 简单扩展属性 @@ -105,17 +111,17 @@ public Product( Guid id ) : base( id ) { [NotMapped] public string TestProperty1 { get => ExtraProperties.GetProperty( nameof( TestProperty1 ) ); - set => ExtraProperties.SetProperty( nameof(TestProperty1), value ); + set => ExtraProperties.SetProperty( nameof( TestProperty1 ), value ); } - private readonly ExtraProperty _property2 = new(nameof( TestProperty2 ) ); + private readonly ExtraProperty _property2 = new( nameof( TestProperty2 ) ); /// /// 对象扩展属性 /// [NotMapped] public ProductItem TestProperty2 { get => _property2.GetProperty( ExtraProperties ); - set => _property2.SetProperty( ExtraProperties,value ); + set => _property2.SetProperty( ExtraProperties, value ); } /// @@ -137,14 +143,14 @@ public ProductItem2 TestProperty4 { set => _property4.SetProperty( ExtraProperties, value ); } - private readonly ExtraProperty> _properties = new( nameof( TestProperties ) ); + private readonly ExtraProperty> _properties = new( nameof( TestProperties ) ); /// /// 对象集合扩展属性 /// [NotMapped] public List TestProperties { get => _properties.GetProperty( ExtraProperties ); - set => _properties.SetProperty( ExtraProperties,value ); + set => _properties.SetProperty( ExtraProperties, value ); } private readonly ExtraProperty _property5 = new( nameof( TestProperty5 ) ); @@ -171,4 +177,18 @@ protected override void AddChanges( Product other ) { AddChange( t => t.LastModificationTime, other.LastModificationTime ); AddChange( t => t.LastModifierId, other.LastModifierId ); } + + /// + /// 测试领域事件方法1 - 添加1个本地事件 TestEvent2 + /// + public void TestDomainEvent1() { + AddDomainEvent( new TestEvent2 { Id = Id, Name = "a" } ); + } + + /// + /// 测试领域事件方法2 - 添加1个集成事件 TestEvent3 + /// + public void TestDomainEvent2() { + AddDomainEvent( new TestEvent3( Id, "a" ) ); + } } \ No newline at end of file diff --git a/test/Util.TestShare/UnitOfWorks/ITestUnitOfWork.cs b/test/Util.TestShare/UnitOfWorks/ITestUnitOfWork.cs index c7141d05e..daf4032b9 100644 --- a/test/Util.TestShare/UnitOfWorks/ITestUnitOfWork.cs +++ b/test/Util.TestShare/UnitOfWorks/ITestUnitOfWork.cs @@ -6,4 +6,10 @@ namespace Util.Tests.UnitOfWorks; /// 工作单元 /// public interface ITestUnitOfWork : IUnitOfWork { +} + +/// +/// 工作单元2 +/// +public interface ITestUnitOfWork2 : IUnitOfWork { } \ No newline at end of file diff --git a/test/Util.TestShare/Util.TestShare.csproj b/test/Util.TestShare/Util.TestShare.csproj index 959452ed1..57c7af72d 100644 --- a/test/Util.TestShare/Util.TestShare.csproj +++ b/test/Util.TestShare/Util.TestShare.csproj @@ -10,5 +10,6 @@ + diff --git a/test/Util.Ui.NgAlain.Tests/Util.Ui.NgAlain.Tests.csproj b/test/Util.Ui.NgAlain.Tests/Util.Ui.NgAlain.Tests.csproj index f2644dab0..207b0e05e 100644 --- a/test/Util.Ui.NgAlain.Tests/Util.Ui.NgAlain.Tests.csproj +++ b/test/Util.Ui.NgAlain.Tests/Util.Ui.NgAlain.Tests.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Ui.NgZorro.Tests/Util.Ui.NgZorro.Tests.csproj b/test/Util.Ui.NgZorro.Tests/Util.Ui.NgZorro.Tests.csproj index f2186aca8..d7a724ac0 100644 --- a/test/Util.Ui.NgZorro.Tests/Util.Ui.NgZorro.Tests.csproj +++ b/test/Util.Ui.NgZorro.Tests/Util.Ui.NgZorro.Tests.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Ui.Tests/Util.Ui.Tests.csproj b/test/Util.Ui.Tests/Util.Ui.Tests.csproj index 7b71164ef..a25b8ec26 100644 --- a/test/Util.Ui.Tests/Util.Ui.Tests.csproj +++ b/test/Util.Ui.Tests/Util.Ui.Tests.csproj @@ -6,7 +6,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Util.Validation.Tests/Util.Validation.Tests.csproj b/test/Util.Validation.Tests/Util.Validation.Tests.csproj index c369ed9d1..7a859c828 100644 --- a/test/Util.Validation.Tests/Util.Validation.Tests.csproj +++ b/test/Util.Validation.Tests/Util.Validation.Tests.csproj @@ -7,9 +7,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive