guqiang 1 сар өмнө
parent
commit
692836d39a
100 өөрчлөгдсөн 4848 нэмэгдсэн , 2 устгасан
  1. 35 0
      Hotline.sln
  2. 23 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Attributes/EntityMapperAttribute.cs
  3. 7 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Class1.cs
  4. 76 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/ActionRequest.cs
  5. 9 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/EntityQueryRequest.cs
  6. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/PageViewResponse.cs
  7. 103 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/Pagination.cs
  8. 17 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Exam.Infrastructure.Data.csproj
  9. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IActionRequest.cs
  10. 6 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IPagedViewResponse.cs
  11. 6 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IQueryRequest.cs
  12. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IViewResponse.cs
  13. 7 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Class1.cs
  14. 8 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Constants/InfrastructureContant.cs
  15. 16 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Enums/ValidationErrorType.cs
  16. 19 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Exam.Infrastructure.Validation.csproj
  17. 73 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Exceptions/DomainException.cs
  18. 67 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Extensions/ValidateResultExtensions.cs
  19. 29 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/BaseValidator.cs
  20. 119 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ErrorMessage.cs
  21. 51 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/IValidationErrors.cs
  22. 52 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidationErrorItem.cs
  23. 111 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidationErrors.cs
  24. 22 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidatorTypeConstants.cs
  25. 7 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Class1.cs
  26. 14 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Exam.Infrastructure.Web.csproj
  27. 123 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/AssemblyUtility.cs
  28. 65 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/ServiceUtility.cs
  29. 10 0
      src/01.Infrastructure/Exam.Infrastructure/Attributes/ReflectIgnoreAttribute.cs
  30. 7 0
      src/01.Infrastructure/Exam.Infrastructure/Class1.cs
  31. 32 0
      src/01.Infrastructure/Exam.Infrastructure/Enums/OperationStatus.cs
  32. 15 0
      src/01.Infrastructure/Exam.Infrastructure/Exam.Infrastructure.csproj
  33. 47 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/EnumExtension.cs
  34. 114 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ExpressionExtensions.cs
  35. 514 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ObjectExtension.cs
  36. 55 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ParameterRebinder.cs
  37. 530 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/StringExtension.cs
  38. 244 0
      src/01.Infrastructure/Exam.Infrastructure/Utilities/BaseAssemblyUtility.cs
  39. 7 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Class1.cs
  40. 9 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Entitys/StatusActionRequest.cs
  41. 22 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Exam.Insfrastructure.Service.csproj
  42. 79 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Extensions/ApiServiceExtension.cs
  43. 26 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IApiService.cs
  44. 6 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IDomainService.cs
  45. 60 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IExamRepository.cs
  46. 17 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IQueryService.cs
  47. 124 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Service/ApiService.cs
  48. 2 2
      src/Hotline.Api/config/appsettings.Development.json
  49. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExamManageService.cs
  50. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExamTagService.cs
  51. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExtractRuleService.cs
  52. 11 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUnExamUserService.cs
  53. 11 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamResultService.cs
  54. 28 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs
  55. 12 0
      src/Hotline.Application/Exam/Interface/Practices/IPracticeService.cs
  56. 20 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionAnswerService.cs
  57. 20 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionKnowladgeService.cs
  58. 20 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionOptionsService.cs
  59. 12 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionService.cs
  60. 20 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionSourcewareService.cs
  61. 20 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionTagService.cs
  62. 20 0
      src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareCategoryService.cs
  63. 19 0
      src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareService.cs
  64. 12 0
      src/Hotline.Application/Exam/Interface/TestPapers/ITestPaperService.cs
  65. 12 0
      src/Hotline.Application/Exam/Interface/Train/ITrainPlanService.cs
  66. 10 0
      src/Hotline.Application/Exam/Interface/Train/ITrainRecordAnswerService.cs
  67. 19 0
      src/Hotline.Application/Exam/Interface/Train/ITrainRecordService.cs
  68. 12 0
      src/Hotline.Application/Exam/Interface/Train/ITrainTemplateService.cs
  69. 11 0
      src/Hotline.Application/Exam/Mappers/MapperConfigs.cs
  70. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionAnswerQueryExtensions.cs
  71. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionKnowladgeQueryExtensions.cs
  72. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionOptionsQueryExtensions.cs
  73. 39 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionQueryExtesions.cs
  74. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionSourcewareQueryExtensions.cs
  75. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionTagQueryExtensions.cs
  76. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Sourcewares/SourcewareCategoryQueryExtensions.cs
  77. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Sourcewares/SourcewareQueryExtensions.cs
  78. 77 0
      src/Hotline.Application/Exam/Service/Questions/QuestionAnswerService.cs
  79. 76 0
      src/Hotline.Application/Exam/Service/Questions/QuestionKnowladgeService.cs
  80. 79 0
      src/Hotline.Application/Exam/Service/Questions/QuestionOptionsService.cs
  81. 145 0
      src/Hotline.Application/Exam/Service/Questions/QuestionService.cs
  82. 76 0
      src/Hotline.Application/Exam/Service/Questions/QuestionSourcewareService.cs
  83. 75 0
      src/Hotline.Application/Exam/Service/Questions/QuestionTagService.cs
  84. 56 0
      src/Hotline.Application/Exam/Service/Sourcewares/SourcewareCategoryService.cs
  85. 80 0
      src/Hotline.Application/Exam/Service/Sourcewares/SourcewareService.cs
  86. 8 0
      src/Hotline.Application/Hotline.Application.csproj
  87. 113 0
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarExtensions.cs
  88. 22 0
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarRepositoryExtensions.cs
  89. 285 0
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarStartupExtensions.cs
  90. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamAnswerRepository.cs
  91. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamManageRepository.cs
  92. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamQuestionScoreRepository.cs
  93. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamTagRepository.cs
  94. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExtractRuleRepository.cs
  95. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IRuleTagRepository.cs
  96. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamItemRepository.cs
  97. 21 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamRepository.cs
  98. 20 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeAnswerRepository.cs
  99. 20 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeQuestionRepository.cs
  100. 20 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeRecordRepository.cs

+ 35 - 0
Hotline.sln

@@ -63,6 +63,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Pdf", "src\Hotline.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hotline.Tests", "test\Hotline.Tests\Hotline.Tests.csproj", "{31855124-4EFC-47B9-A4D5-64822DE036E6}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exam.Infrastructure", "src\01.Infrastructure\Exam.Infrastructure\Exam.Infrastructure.csproj", "{FCAC41BE-6E47-700F-E226-BD01CAB4856A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exam.Infrastructure.Data", "src\01.Infrastructure\Exam.Infrastructure.Data\Exam.Infrastructure.Data.csproj", "{A0A928D1-0AF2-AECD-BF87-0E85442F9E47}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exam.Infrastructure.Validation", "src\01.Infrastructure\Exam.Infrastructure.Validation\Exam.Infrastructure.Validation.csproj", "{FB1785EE-2C30-E682-F02A-32A37C351273}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exam.Infrastructure.Web", "src\01.Infrastructure\Exam.Infrastructure.Web\Exam.Infrastructure.Web.csproj", "{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exam.Insfrastructure.Service", "src\01.Infrastructure\Exam.Insfrastructure.Service\Exam.Insfrastructure.Service.csproj", "{1B71F63E-916A-185B-6A02-31C10FDED2AD}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -165,6 +175,26 @@ Global
 		{31855124-4EFC-47B9-A4D5-64822DE036E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{31855124-4EFC-47B9-A4D5-64822DE036E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{31855124-4EFC-47B9-A4D5-64822DE036E6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FCAC41BE-6E47-700F-E226-BD01CAB4856A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FCAC41BE-6E47-700F-E226-BD01CAB4856A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FCAC41BE-6E47-700F-E226-BD01CAB4856A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FCAC41BE-6E47-700F-E226-BD01CAB4856A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A0A928D1-0AF2-AECD-BF87-0E85442F9E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A0A928D1-0AF2-AECD-BF87-0E85442F9E47}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A0A928D1-0AF2-AECD-BF87-0E85442F9E47}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A0A928D1-0AF2-AECD-BF87-0E85442F9E47}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FB1785EE-2C30-E682-F02A-32A37C351273}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FB1785EE-2C30-E682-F02A-32A37C351273}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FB1785EE-2C30-E682-F02A-32A37C351273}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FB1785EE-2C30-E682-F02A-32A37C351273}.Release|Any CPU.Build.0 = Release|Any CPU
+		{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1B71F63E-916A-185B-6A02-31C10FDED2AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1B71F63E-916A-185B-6A02-31C10FDED2AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1B71F63E-916A-185B-6A02-31C10FDED2AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1B71F63E-916A-185B-6A02-31C10FDED2AD}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -198,6 +228,11 @@ Global
 		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{3AB75B51-A69D-4145-A564-1D9D1695992E} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{31855124-4EFC-47B9-A4D5-64822DE036E6} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
+		{FCAC41BE-6E47-700F-E226-BD01CAB4856A} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{A0A928D1-0AF2-AECD-BF87-0E85442F9E47} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{FB1785EE-2C30-E682-F02A-32A37C351273} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{19E169D6-7A9A-26D1-6C57-4DDD1D32FF72} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{1B71F63E-916A-185B-6A02-31C10FDED2AD} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

+ 23 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Attributes/EntityMapperAttribute.cs

@@ -0,0 +1,23 @@
+namespace Exam.Infrastructure.Data.Attributes
+{
+    /// <summary>
+    /// 实体映射属性
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class)]
+    public class EntityMapperAttribute:Attribute
+    {
+        private readonly string _entityName;
+        public string EntityName
+        {
+            get
+            {
+                return _entityName;
+            }
+        }
+
+        public EntityMapperAttribute(string entityName)
+        {
+            _entityName = entityName;
+        }
+    }
+}

+ 7 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Class1.cs

@@ -0,0 +1,7 @@
+namespace Exam.Infrastructure.Data
+{
+    public class Class1
+    {
+
+    }
+}

+ 76 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Entity/ActionRequest.cs

@@ -0,0 +1,76 @@
+using Exam.Infrastructure.Data.Interface;
+using Exam.Infrastructure.Enums;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Exam.Infrastructure.Data.Entity
+{
+    /// <summary>
+    /// Dto基类
+    /// </summary>
+    public class ActionRequest:IActionRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        [Description("排序")]
+        public int SortIndex { get; set; }
+
+        /// <summary>
+        /// 状态
+        /// </summary>
+        [Description("状态")]
+        public int Status { get; set; }
+
+        /// <summary>
+        /// 用户ID
+        /// </summary>
+        [Description("用户ID")]
+        [JsonIgnore]
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// 用户名称
+        /// </summary>
+        [Description("用户名称")]
+        [JsonIgnore]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 组织Id
+        /// </summary>
+        [Description("组织Id")]
+        [JsonIgnore]
+        public string OrgId { get; set; }
+
+        /// <summary>
+        /// 组织名称
+        /// </summary>
+        [Description("组织名称")]
+        [JsonIgnore]
+        public string OrgName { get; set; }
+        /// <summary>
+        /// 组织层级
+        /// </summary>
+        [Description("组织层级")]
+        [JsonIgnore]
+        public int OrgLevel { get; set; }
+
+        /// <summary>
+        /// 操作状态
+        /// </summary>
+        [Description("操作状态")]
+        public OperationStatus OperationStatus { get; set; }
+    }
+}

+ 9 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Entity/EntityQueryRequest.cs

@@ -0,0 +1,9 @@
+namespace Exam.Infrastructure.Data.Entity
+{
+    public class EntityQueryRequest
+    {
+        public string Id { get; set; }
+
+        public List<string> Ids { get; set; }
+    }
+}

+ 13 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Entity/PageViewResponse.cs

@@ -0,0 +1,13 @@
+using Exam.Infrastructure.Data.Interface;
+
+namespace Exam.Infrastructure.Data.Entity
+{
+    public class PageViewResponse<T>:IPagedViewResponse 
+        where T: IViewResponse
+    {
+        public Pagination Pagination { get; set; }
+
+        public IReadOnlyList<T> Items { get; set; }
+
+    }
+}

+ 103 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Entity/Pagination.cs

@@ -0,0 +1,103 @@
+using Newtonsoft.Json;
+
+namespace Exam.Infrastructure.Data.Entity
+{
+    public class Pagination
+    {
+        /// <summary>
+        /// 当前页面索引
+        /// </summary>
+        [JsonProperty(PropertyName = "pageNum")]
+        public int PageIndex { get; set; }
+
+        /// <summary>
+        /// 页面大小
+        /// </summary>
+        public int PageSize { get; set; }
+
+        /// <summary>
+        /// 数据总量
+        /// </summary>
+        [JsonProperty(PropertyName = "total")]
+        public int TotalCount { get; set; }
+
+        /// <summary>
+        /// 页面首页
+        /// </summary>
+        public int FirstPageIndex
+        {
+            get
+            {
+                return 1;
+            }
+        }
+
+        /// <summary>
+        /// 页面总页数
+        /// </summary>
+        public int PageCount
+        {
+            get { return PageSize == 0 ? PageSize : TotalCount / PageSize + (TotalCount % PageSize == 0 ? 0 : 1); }
+        }
+
+        /// <summary>
+        /// 偏移量
+        /// </summary>
+        [JsonIgnore]
+        public int SkipCount
+        {
+            get { return PageIndex <= 0 ? PageSize : (PageIndex - 1) * PageSize; }
+        }
+
+        public Pagination(int pageIndex, int pageSize, int totalCount = 0)
+        {
+            PageIndex = pageIndex;
+            PageSize = pageSize;
+            TotalCount = totalCount;
+        }
+
+    }
+
+    /// <summary>
+    /// 默认分页
+    /// </summary>
+    public class DefaultPagination : Pagination
+    {
+        public DefaultPagination()
+            : base(PaginationValueConstant.PageIndex, PaginationValueConstant.PageSize)
+        {
+
+        }
+
+        public DefaultPagination(int pageIndex, int pageSize, int totalCount = 0)
+            : base(pageIndex, pageSize, totalCount)
+        {
+        }
+    }
+
+    /// <summary>
+    /// 最大分页数
+    /// </summary>
+    public class DefaultMaxPagination : Pagination
+    {
+        public DefaultMaxPagination()
+            : base(PaginationValueConstant.PageIndex, PaginationValueConstant.PageMaxSize)
+        {
+
+        }
+
+        public DefaultMaxPagination(int pageIndex, int pageSize, int totalCount = 0)
+            : base(pageIndex, pageSize, totalCount)
+        {
+        }
+    }
+
+    public class PaginationValueConstant
+    {
+        public const int PageIndex = 1;
+
+        public const int PageSize = 10;
+
+        public const int PageMaxSize = 100;
+    }
+}

+ 17 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Exam.Infrastructure.Data.csproj

@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Exam.Infrastructure\Exam.Infrastructure.csproj" />
+  </ItemGroup>
+
+</Project>

+ 13 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IActionRequest.cs

@@ -0,0 +1,13 @@
+using System.ComponentModel;
+
+namespace Exam.Infrastructure.Data.Interface
+{
+    public interface IActionRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+    }
+}

+ 6 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IPagedViewResponse.cs

@@ -0,0 +1,6 @@
+namespace Exam.Infrastructure.Data.Interface
+{
+    public interface IPagedViewResponse
+    {
+    }
+}

+ 6 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IQueryRequest.cs

@@ -0,0 +1,6 @@
+namespace Exam.Infrastructure.Data.Interface
+{
+    public interface IQueryRequest
+    {
+    }
+}

+ 13 - 0
src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IViewResponse.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Exam.Infrastructure.Data.Interface
+{
+    public interface IViewResponse
+    {
+        public string Id { get; set; }
+    }
+}

+ 7 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Class1.cs

@@ -0,0 +1,7 @@
+namespace Exam.Infrastructure.Validation
+{
+    public class Class1
+    {
+
+    }
+}

+ 8 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Constants/InfrastructureContant.cs

@@ -0,0 +1,8 @@
+namespace Exam.Infrastructure.Validation.Constants
+{
+    public class InfrastructureContant
+    {
+        public const string SystemError = "SystemError";
+        public const string SystemErrorDescription = "系统错误";
+    }
+}

+ 16 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Enums/ValidationErrorType.cs

@@ -0,0 +1,16 @@
+using System.ComponentModel;
+
+namespace Exam.Infrastructure.Validation.Enums
+{
+    public enum ValidationErrorType
+    {
+        [Description("Body")]
+        Body = 1,
+
+        [Description("Items")]
+        Items = 2,
+
+        [Description("Accounts")]
+        Accounts = 3,
+    }
+}

+ 19 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Exam.Infrastructure.Validation.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FluentValidation" Version="11.10.0" />
+    <PackageReference Include="XF.Domain" Version="1.0.11" />
+    <PackageReference Include="XF.Domain.Repository" Version="1.0.8" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Exam.Infrastructure\Exam.Infrastructure.csproj" />
+  </ItemGroup>
+
+</Project>

+ 73 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Exceptions/DomainException.cs

@@ -0,0 +1,73 @@
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Validation.Enums;
+using Exam.Infrastructure.Validation.Validation;
+using System.Linq.Expressions;
+
+namespace Exam.Infrastructure.Validation.Exceptions
+{
+    /// <summary>
+    /// 业务逻辑异常
+    /// </summary>
+    [Serializable]
+    public class DomainException : Exception
+    {
+        /// <summary>
+        /// 异常对应领域实体属性名称(可为null)
+        /// </summary>
+        protected string EffectPropertyName
+        {
+            get;
+            set;
+        }
+
+        public IValidationErrors ValidationErrors { get; private set; }
+
+        public DomainException()
+        {
+            ValidationErrors = new ValidationErrors();
+        }
+
+        protected DomainException(string message) : base(message)
+        {
+            ValidationErrors = new ValidationErrors();
+        }
+
+        public DomainException(IValidationErrors validationErrors, ValidationErrorType errorType = ValidationErrorType.Body)
+        {
+            ValidationErrors = validationErrors;
+            foreach (var validationError in ValidationErrors.ErrorItems)
+            {
+                validationError.ErrorType = errorType.GetDescription();
+            }
+        }
+
+        public void AddErrors(IValidationErrors validationErrors)
+        {
+            ValidationErrors.AddErrors(validationErrors.ErrorItems);
+        }
+
+        public IValidationErrors AddError<TObject, TProperty>(Expression<Func<TObject, TProperty>> expression, object attemptedValue, string errorMessage, bool customState = false)
+        {
+            var memberExpression = expression.Body as MemberExpression;
+            if (memberExpression != null)
+                return ValidationErrors.AddError(memberExpression.Member.Name, errorMessage, attemptedValue, customState);
+            return new ValidationErrors();
+        }
+
+        public IValidationErrors AddError(string propertyName, string errorMessage, string errorCode, string errorDescription, object attemptedValue)
+        {
+            return ValidationErrors.AddDomainError(propertyName, errorMessage, errorCode, errorDescription, attemptedValue);
+        }
+
+        public static TException Create<TException>(IValidationErrors errors) where TException : DomainException, new()
+        {
+            var exception = new TException();
+            if (errors != null)
+            {
+                exception.ValidationErrors = errors;
+            }
+
+            return exception;
+        }
+    }
+}

+ 67 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Extensions/ValidateResultExtensions.cs

@@ -0,0 +1,67 @@
+using Exam.Infrastructure.Validation.Enums;
+using Exam.Infrastructure.Validation.Exceptions;
+using Exam.Infrastructure.Validation.Validation;
+using FluentValidation;
+using FluentValidation.Results;
+
+namespace Exam.Infrastructure.Validation.Extensions
+{
+    public static class ValidateResultExtensions
+    {
+        public static IValidationErrors ToValidationError(this ValidationResult validationResult)
+        {
+            var validationError = new ValidationErrors();
+
+            foreach (var failure in validationResult.Errors)
+            {
+                validationError.AddError(failure.PropertyName, failure.AttemptedValue, failure.ErrorMessage, false);
+            }
+
+            return validationError;
+        }
+
+        public static IValidationErrors DoValidate<T>(this IValidator<T> validator, T instance, string ruleSet = null)
+        {
+            var validationResult = validator.Validate(instance, v => {
+                v.IncludeRuleSets(ruleSet);
+            });
+
+            var validationError = new ValidationErrors();
+
+            foreach (var failure in validationResult.Errors)
+            {
+                validationError.AddError(failure.PropertyName, failure.AttemptedValue, failure.ErrorMessage, false);
+            }
+
+            return validationError;
+        }
+
+        public static async Task<IValidationErrors> DoValidateAsync<T>(this IValidator<T> validator, T instance, string ruleSet = null, object validateKey = null)
+        {
+            var validationResult = await validator.ValidateAsync(instance, v => {
+                v.IncludeRuleSets(ruleSet);
+            });
+
+            var validationError = new ValidationErrors();
+            validateKey = validateKey ?? string.Empty;
+
+            foreach (var failure in validationResult.Errors)
+            {
+                validationError.AddError(failure.PropertyName, failure.AttemptedValue, failure.ErrorMessage, false, validateKey.ToString());
+            }
+
+            return validationError;
+        }
+
+        public static async Task ToValidateResult<T>(this IValidator<T> validator, T instance, string ruleSet = null, object validateKey = null)
+        {
+
+            var validate = await validator.DoValidateAsync(instance, ruleSet, validateKey);
+
+            if (!validate.IsValid)
+            {
+                throw new DomainException(validate,ValidationErrorType.Body);
+            }
+        }
+    }
+}

+ 29 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/BaseValidator.cs

@@ -0,0 +1,29 @@
+using Exam.Infrastructure.Extensions;
+using FluentValidation;
+using XF.Domain.Repository;
+
+namespace Exam.Infrastructure.Validation.Validation
+{
+    public class BaseValidator<T>:AbstractValidator<T> where T:CreationEntity
+    {
+
+        protected virtual void ValidateRuleWithModify()
+        {
+        }
+
+        protected virtual void ValidateRuleWithAdd()
+        {
+        }
+
+        protected virtual void BaseValidateRule()
+        {
+            RuleFor(m => m.Id).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.Id))));
+            RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreationTime))));
+            RuleFor(m => m.CreatorId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreatorId))));
+            RuleFor(m => m.CreatorName).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreatorName))));
+            RuleFor(m => m.CreatorOrgId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreatorOrgId))));
+            RuleFor(m => m.CreatorOrgLevel).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreatorOrgLevel))));
+            RuleFor(m => m.CreatorOrgName).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(CreationEntity.CreatorOrgName))));
+        }
+    }
+}

+ 119 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ErrorMessage.cs

@@ -0,0 +1,119 @@
+namespace Exam.Infrastructure.Validation.Validation
+{
+    public class ErrorMessage
+    {
+
+        public const string Empty = "不能为空";
+
+        public const string IsNameRequired = "名称不能为空";
+
+        public const string IsNameRepeat = "名称已重复";
+
+        public const string IsChildNodeExsit = "存在子节点";
+
+        public const string IsSortIndexRequired = "排序不能为空";
+
+        public const string IsAccountRequired = "帐号不能为空";
+
+        public const string IsAccountRepeat = "帐号已重复";
+
+        public const string IsCodeRequired = "编号不能为空";
+
+        public const string IsCodeRepeat = "编号已重复";
+
+        public const string IsPasswordRequired = "密码不能为空";
+
+        public const string IsStatusRequired = "状态必须选择";
+
+        public const string IsIntegerRequired = "请输入一个有效的整数";
+
+        public const string IsMobileRequired = "请输入一个有效的电话号码";
+
+        public const string IsContentRequired = "内容不能为空";
+
+        public const string IsFirstWordRequired = "拼音首字母不能为空";
+
+        public const string IsProvinceRequired = "省不能为空";
+
+        public const string IsCityRequired = "市不能为空";
+
+        public const string IsRegioRequired = "地区不能为空";
+
+        public const string IsMobilePatternError = "手机号码格式不正确,请输入11位有效手机号码";
+
+        public const string IsTelPatternError = "电话号码格式不正确,请输入如:028-XXXXXXXX";
+
+        public const string IsEmailPatternError = "邮箱格式不正确";
+
+        public const string IsZipCodePatternError = "请输入6位正确邮政编码";
+
+        public const string IsAddressLengthError = "地址字符数范围为1-199";
+
+        public const string IsRequired = "{0}不能为空";
+
+        public const string IsBillTimeValid = "{0}不能超过当前日期";
+
+        public const string IsGreaterZero = "{0}须大于零";
+
+        public const string IsGreaterEqualZero = "{0}须大于等于零";
+
+        public const string IsRepeat = "{0}已重复";
+
+        public const string IsRepeated = "已重复";
+
+        public const string IsPositiveNumber = "{0}必须是大于零的正整数";
+
+        public const string IsDecimalNumber = "{0}必须是数字";
+
+        public const string IsLengthError = "输入字符不能超过{0}个";
+        public const string IsLengthGreater = "输入字符至少{0}个";
+
+        public const string IsStringLengthError = "输入字符不能超过{1}个";
+
+        public const string IsCodePatternError = "{0}格式不正确,只能输入字母和数字";
+
+        public const string IsBankPatternError = "银行卡号格式不正确,只能输入数字16位或19位";
+
+        public const string IsServerPatternError = "{0}格式不正确,只能输入字母、数字、点";
+
+        public const string IsPwdPatternError = "{0}格式不正确,只能输入字母、数字、特殊字符(! @ # $ % ?等)";
+
+        public const string IsInUse = "{0}正在被使用";
+        public const string IsConfirmPasswordCorrect = "确认密码和密码不一致";
+
+        public const string IsNotEqual = "{0}与{1}不一致";
+        public const string PasswordError = "密码错误";
+
+        public const string AccountNotExists = "该用户不存在";
+
+        public const string InvoiceRepeated = "发票已被认领,请勿重复认领";
+
+        public const string InvoiceNotExist = "发票不存在";
+
+        public const string PublishStatus = "发布状态不允许操作";
+
+        public const string EmptyUploadFile = "上传文件件为空";
+
+        public const string ServiceError = "服务错误";
+    }
+
+    public static class RegexConstant
+    {
+        public const string Email = @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
+        public const string Mobile = @"^1[3-9]\d{9}$";
+        public const string Integer = @"^[0-9]{1,10}$";
+        public const string NumberAndLetter = @"^[A-Za-z0-9]+$";
+        public const string BankAccount = @"^(\d{16}|\d{19})$";
+        public const string NumberAnLetterAndDian = @"^[A-Za-z0-9.]+$";
+        public const string Pwd = @"/^\w{6,20}$/";
+        public const string IsNumberAndCharctor = @"^(?![0-9]+$)(?![a-zA-Z]+$)[0-9a-zA-Z]+$";
+    }
+
+    public static class PasswordLengthConstant
+    {
+
+        public const int MinPassowrdLength = 8;
+
+        public const int MaxPassowrdLength = 18;
+    }
+}

+ 51 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/IValidationErrors.cs

@@ -0,0 +1,51 @@
+using Exam.Infrastructure.Validation.Constants;
+
+namespace Exam.Infrastructure.Validation.Validation
+{
+    public interface IValidationErrors
+    {
+
+        IValidationErrors AddErrors(IEnumerable<ValidationErrorItem> errorItems);
+
+        /// <summary>
+        /// 添加领域驱动异常, 非捕获Exception引起的异常
+        /// </summary>
+        /// <param name="propertyName">引起异常的属性名称</param>
+        /// <param name="errorMessage">具体异常信息</param>
+        /// <param name="errorCode">异常代码</param>
+        /// <param name="errorDescription">有关异常的详细描述或发生验证异常的字段描述</param>
+        /// <param name="attemptedValue">试图用于描述异常信息的值</param>
+        /// <returns></returns>
+        IValidationErrors AddDomainError(string propertyName, string errorMessage, string errorCode = null, string errorDescription = null, object attemptedValue = null);
+
+        /// <summary>
+        /// 添加通用异常
+        /// </summary>
+        /// <param name="propertyName">引起异常的属性名称</param>
+        /// <param name="errorMessage">具体异常信息</param>
+        /// <param name="errorCode">异常代码</param>
+        /// <param name="errorStackTrace">捕获异常堆栈信息</param>
+        /// <param name="errorDescription">有关异常的详细描述或发生验证异常的字段描述</param>
+        /// <param name="attemptedValue">试图用于描述异常信息的值</param>
+        /// <returns></returns>
+        /// <returns></returns>
+        IValidationErrors AddError(string propertyName, string errorMessage, object attemptedValue, bool customState = false);
+
+        /// <summary>
+        /// 添加系统异常
+        /// </summary>
+        /// <param name="propertyName">默认为系统异常</param>
+        /// <param name="errorMessage">系统错误</param>
+        /// <param name="attemptedValue">试图用于描述异常信息的值</param>
+        /// <returns></returns>
+        IValidationErrors AddSystemError(string propertyName = InfrastructureContant.SystemError, string errorMessage = InfrastructureContant.SystemErrorDescription,
+            object attemptedValue = null, bool customState = false);
+
+        /// <summary>
+        /// 结果是否正确
+        /// </summary>
+        bool IsValid { get; }
+
+        IEnumerable<ValidationErrorItem> ErrorItems { get; }
+    }
+}

+ 52 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidationErrorItem.cs

@@ -0,0 +1,52 @@
+namespace Exam.Infrastructure.Validation.Validation
+{
+    /// <summary>
+    /// 验证错误明细
+    /// </summary>
+    public class ValidationErrorItem
+    {
+        public ValidationErrorItem(string propertyName, string errorMessage, object attemptedValue, bool customState, string errorKey = "")
+        {
+            PropertyName = propertyName;
+            ErrorMessage = errorMessage;
+            AttemptedValue = attemptedValue;
+            CustomState = customState;
+            ErrorKey = errorKey;
+        }
+
+        /// <summary>
+        /// 属性名称.
+        /// </summary>
+        public string PropertyName { get; set; }
+
+        /// <summary>
+        /// 错误信息
+        /// </summary>
+        public string ErrorMessage { get; private set; }
+
+        /// <summary>
+        /// 尝试值
+        /// </summary>
+        public object AttemptedValue { get; private set; }
+
+        /// <summary>
+        /// 客户端状态.
+        /// </summary>
+        public bool CustomState { get; set; }
+
+        /// <summary>
+        /// 错误类型
+        /// </summary>
+        public string ErrorType { get; set; }
+
+        /// <summary>
+        /// 错误关键字
+        /// </summary>
+        public string ErrorKey { get; set; }
+
+        /// <summary>
+        /// 错误代码
+        /// </summary>
+        public string ErrorCode { get; private set; }
+    }
+}

+ 111 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidationErrors.cs

@@ -0,0 +1,111 @@
+using Exam.Infrastructure.Validation.Constants;
+
+namespace Exam.Infrastructure.Validation.Validation
+{
+    /// <summary>
+    /// 验证错误
+    /// </summary>
+    [Serializable]
+    public class ValidationErrors : IValidationErrors
+    {
+
+        private readonly List<ValidationErrorItem> _errorItemList = new List<ValidationErrorItem>();
+
+        public bool IsValid
+        {
+            get
+            {
+                return _errorItemList.Count == 0;
+            }
+        }
+
+        /// <summary>
+        /// 错误明细
+        /// </summary>
+        public IEnumerable<ValidationErrorItem> ErrorItems { get { return _errorItemList; } }
+
+        /// <summary>
+        /// 添加系统错误
+        /// </summary>
+        /// <param name="propertyName"></param>
+        /// <param name="errorMessage"></param>
+        /// <param name="attemptedValue"></param>
+        /// <param name="customState"></param>
+        /// <returns></returns>
+        public IValidationErrors AddSystemError(string propertyName = InfrastructureContant.SystemError, string errorMessage = InfrastructureContant.SystemErrorDescription,
+            object attemptedValue = null, bool customState = false)
+        {
+            return AddError(propertyName, errorMessage, attemptedValue, customState);
+        }
+
+        /// <summary>
+        /// 添加错误
+        /// </summary>
+        /// <param name="errorItems"></param>
+        /// <returns></returns>
+        public IValidationErrors AddErrors(IEnumerable<ValidationErrorItem> errorItems)
+        {
+            if (errorItems == null)
+            {
+                return this;
+            }
+            foreach (var errorItem in errorItems)
+            {
+                if (errorItem.CustomState)
+                {
+                    errorItem.PropertyName = "IsAlter";
+                }
+                _errorItemList.Add(errorItem);
+            }
+
+            return this;
+        }
+
+        /// <summary>
+        /// 添加错误
+        /// </summary>
+        /// <param name="propertyName"></param>
+        /// <param name="attemptedValue"></param>
+        /// <param name="errorMessage"></param>
+        /// <param name="customState"></param>
+        /// <param name="validateKey"></param>
+        /// <returns></returns>
+        public IValidationErrors AddError(string propertyName, object attemptedValue, string errorMessage = null, bool customState = false, string validateKey = "")
+        {
+            var errorItem = new ValidationErrorItem(propertyName, errorMessage, attemptedValue, customState, validateKey);
+            _errorItemList.Add(errorItem);
+            return this;
+        }
+
+        /// <summary>
+        /// 添加错误
+        /// </summary>
+        /// <param name="propertyName"></param>
+        /// <param name="errorMessage"></param>
+        /// <param name="attemptedValue"></param>
+        /// <param name="customState"></param>
+        /// <returns></returns>
+        public IValidationErrors AddError(string propertyName, string errorMessage, object attemptedValue, bool customState = false)
+        {
+            var errorItem = new ValidationErrorItem(propertyName, errorMessage, attemptedValue, customState);
+            _errorItemList.Add(errorItem);
+            return this;
+        }
+
+        /// <summary>
+        /// 添加领域业务错误
+        /// </summary>
+        /// <param name="propertyName"></param>
+        /// <param name="errorMessage"></param>
+        /// <param name="errorCode"></param>
+        /// <param name="errorDescription"></param>
+        /// <param name="attemptedValue"></param>
+        /// <returns></returns>
+        public IValidationErrors AddDomainError(string propertyName, string errorMessage, string errorCode = null, string errorDescription = null, object attemptedValue = null)
+        {
+            var errorItem = new ValidationErrorItem(propertyName, errorMessage, attemptedValue, false, errorCode);
+            _errorItemList.Add(errorItem);
+            return this;
+        }
+    }
+}

+ 22 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Validation/ValidatorTypeConstants.cs

@@ -0,0 +1,22 @@
+namespace Exam.Infrastructure.Validation.Validation
+{
+    /// <summary>
+    /// 验证类型衡量
+    /// </summary>
+    public class ValidatorTypeConstants
+    {
+        public const string Create = "Create";
+
+        public const string Remove = "Remove";
+
+        public const string Modify = "Modify";
+
+        public const string AddOrModify = "AddOrModify";
+
+        public const string ChangePassword = "ChangePassword";
+
+        public const string Login = "Login";
+
+        public const string ResetPassword = "ResetPassword";
+    }
+}

+ 7 - 0
src/01.Infrastructure/Exam.Infrastructure.Web/Class1.cs

@@ -0,0 +1,7 @@
+namespace Exam.Infrastructure.Web
+{
+    public class Class1
+    {
+
+    }
+}

+ 14 - 0
src/01.Infrastructure/Exam.Infrastructure.Web/Exam.Infrastructure.Web.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Exam.Infrastructure\Exam.Infrastructure.csproj" />
+    <ProjectReference Include="..\Exam.Insfrastructure.Service\Exam.Insfrastructure.Service.csproj" />
+  </ItemGroup>
+
+</Project>

+ 123 - 0
src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/AssemblyUtility.cs

@@ -0,0 +1,123 @@
+using Exam.Infrastructure.Utilities;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace Exam.Infrastructure.Web.Utilities
+{
+    public class AssemblyUtility : BaseAssemblyUtility
+    {
+
+
+        private static string[] FinderFilter
+        {
+            get
+            {
+                return new string[] { API, CLIENT, EXTENSIONS };
+            }
+        }
+        private static string[] ModuleFilter
+        {
+            get
+            {
+                return new string[] { @"^Exam\.(.*\.)?Application", @"^Exam\.(.*\.)?Share" };
+            }
+        }
+
+        public static List<Assembly> GetAssembly(string path)
+        {
+            //dynamic type = (new Program()).GetType();
+            string currentDirectory = Path.GetDirectoryName(path);
+            var files = Directory.GetFiles(currentDirectory, ALL);
+            var assemblys = new List<Assembly>();
+
+            foreach (var file in files)
+            {
+                assemblys.Add(Assembly.LoadFrom(file));
+            }
+
+            return assemblys;
+        }
+
+        public static List<Assembly> CreateModules(List<Assembly> assemblies)
+        {
+            List<Assembly> modules = new List<Assembly>();
+            foreach (var filter in ModuleFilter)
+            {
+                CreateModulesByFilter(assemblies, filter);
+            }
+            return modules;
+        }
+
+        public static List<Assembly> CreateModulesByFilter(List<Assembly> assemblies, string filter)
+        {
+            List<Assembly> modules = new List<Assembly>();
+            modules.AddRange(
+                assemblies.Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.CodeBase), filter)));
+            return modules;
+        }
+
+        public static Dictionary<string, List<Assembly>> CreateFinders(List<Assembly> assemblies)
+        {
+            Dictionary<string, List<Assembly>> finders = new Dictionary<string, List<Assembly>>();
+            foreach (var filter in FinderFilter)
+            {
+                if (!finders.ContainsKey(filter))
+                {
+                    finders[filter] = new List<Assembly>();
+                }
+                finders[filter]
+                    .AddRange(assemblies.Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.CodeBase), filter)));
+            }
+            return finders;
+        }
+
+
+
+        public static Tuple<Dictionary<string, List<Assembly>>, Assembly[]> ScannerAssemblies(string path)
+        {
+            var assemblies = GetAssembly(path);
+
+            var finders = CreateFinders(assemblies);
+
+            var modules = CreateModules(assemblies);
+
+            return new Tuple<Dictionary<string, List<Assembly>>, Assembly[]>(finders, modules.ToArray());
+        }
+
+        public static List<Type> GetTypeToRegister<TEntity>(List<Assembly> assemblies, string filter)
+        {
+            var types = new List<Type>();
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                types.AddRange(currentAssembly.GetTypes().Where(q => q.GetInterface(typeof(TEntity).FullName) != null));
+
+            }
+
+            return types;
+        }
+
+        public static List<TEntity> CreateInstance<TEntity>(List<Assembly> assemblies, string filter)
+        {
+            var instatnces = new List<TEntity>();
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                var types = currentAssembly.GetTypes().Where(q => q.GetInterface(typeof(TEntity).FullName) != null);
+
+                foreach (var type in types)
+                {
+                    instatnces.Add((TEntity)currentAssembly.CreateInstance(type.FullName));
+                }
+
+            }
+
+            return instatnces;
+        }
+    }
+}

+ 65 - 0
src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/ServiceUtility.cs

@@ -0,0 +1,65 @@
+using Exam.Infrastructure.Web.Utilities;
+using Exam.Insfrastructure.Service.Interface;
+using Microsoft.Extensions.DependencyInjection;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace Exam.Insfrastructure.Service.Utilities
+{
+    public static class ServiceUtility
+    {
+        public static IServiceCollection Services { get; set; }
+
+        public static void RegisterService(this IServiceCollection services)
+        {
+            var assemblies = AssemblyUtility.GetAssembly(Assembly.GetExecutingAssembly().Location);
+
+            var domainEvents = new List<IDomainService>();
+
+            var apiServiceAssemblies = assemblies.Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.CodeBase), AssemblyUtility.VITRUALAPPLICATION)).ToList();
+
+            foreach (var assembly in apiServiceAssemblies)
+            {
+                try
+                {
+                    
+                    var types = assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IDomainService)));
+
+                    if (types == null || !types.Any()) continue;
+
+                    var implements = types.Where(t => t.IsInterface != true);
+
+                    var interfaces = types.Where(t => t.IsInterface == true);
+                    //var eventTypes = assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IBaseEvent)));
+
+                    foreach (var item in interfaces)
+                    {
+                        var implement = implements.FirstOrDefault(t => t.GetInterfaces().Contains(item));
+                        if (implement != null)
+                        {
+                            services.AddScoped(item, implement);
+                        }
+                    }
+
+                    Services = services;
+                }
+                catch (Exception ex)
+                {
+                    continue;
+                }
+            }
+        }
+
+        public static T GetServic<T>()
+        {
+            if (Services != null)
+            {
+                var provider = Services.BuildServiceProvider();
+
+                return provider.GetService<T>();
+            }
+
+            return default(T);
+        }
+    }
+}

+ 10 - 0
src/01.Infrastructure/Exam.Infrastructure/Attributes/ReflectIgnoreAttribute.cs

@@ -0,0 +1,10 @@
+namespace Exam.Infrastructure.Attributes
+{
+    /// <summary>
+    /// 反射时如有此属性,则不被反射
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property, Inherited = true)]
+    public class ReflectIgnoreAttribute:Attribute
+    {
+    }
+}

+ 7 - 0
src/01.Infrastructure/Exam.Infrastructure/Class1.cs

@@ -0,0 +1,7 @@
+namespace Exam.Infrastructure
+{
+    public class Class1
+    {
+
+    }
+}

+ 32 - 0
src/01.Infrastructure/Exam.Infrastructure/Enums/OperationStatus.cs

@@ -0,0 +1,32 @@
+using System.ComponentModel;
+
+namespace Exam.Infrastructure.Enums
+{
+    /// <summary>
+    /// 操作状态
+    /// </summary>
+    [Description("操作状态")]
+    public enum OperationStatus
+    {
+        /// <summary>
+        /// 新增
+        /// </summary>
+        [Description("新增")]
+        Add=1,
+        /// <summary>
+        /// 修改
+        /// </summary>
+        [Description("修改")]
+        Update =2,
+        /// <summary>
+        /// 删除
+        /// </summary>
+        [Description("删除")]
+        Delete =3,
+        /// <summary>
+        /// 默认
+        /// </summary>
+        [Description("默认")]
+        Default = 0
+    }
+}

+ 15 - 0
src/01.Infrastructure/Exam.Infrastructure/Exam.Infrastructure.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
+  </ItemGroup>
+
+</Project>

+ 47 - 0
src/01.Infrastructure/Exam.Infrastructure/Extensions/EnumExtension.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Exam.Infrastructure.Extensions
+{
+    public static class EnumExtension
+    {
+        /// <summary>
+        /// 数字转换为枚举
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="enumValue"></param>
+        /// <returns></returns>
+        public static T ToEnum<T>(this int enumValue)
+        {
+            return (T)Enum.Parse(typeof(T), enumValue.ToString());
+        }
+
+        /// <summary>
+        /// 获取枚举的描述
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static string GetDescription(this Enum value)
+        {
+            var attribute = GetAttribute<DescriptionAttribute>(value);
+
+            return attribute.Description;
+        }
+
+        /// <summary>
+        /// 获取枚举的属性
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static T GetAttribute<T>(this Enum value) where T : Attribute
+        {
+            var field = value.GetType().GetField(value.ToString());
+            return Attribute.GetCustomAttribute(field, typeof(T)) as T;
+        }
+    }
+}

+ 114 - 0
src/01.Infrastructure/Exam.Infrastructure/Extensions/ExpressionExtensions.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Exam.Infrastructure.Extensions
+{
+    public static class ExpressionExtensions
+    {
+        /// <summary>
+        /// 合并表达式, 其中master为主表达式, slave表达式中的Lambda参数合并后会依从左至右的顺序在slave表达式中进行替换
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="masterExpr">主表达式</param>
+        /// <param name="slaveExpr">合并入主表达式的从表达式</param>
+        /// <param name="mergeExpr">合并表达式算法, 一般为按位表达式BinaryExpression, 例如Expression.Or或者Expression.And等</param>
+        /// <returns></returns>
+        private static Expression<T> Compose<T>(this Expression<T> masterExpr, Expression<T> slaveExpr, Func<Expression, Expression, Expression> mergeExpr)
+        {
+            var replaceExpr = ParameterRebinder.ReplaceParameters<T>(slaveExpr, masterExpr);
+
+            return Expression.Lambda<T>(mergeExpr(masterExpr.Body, replaceExpr), masterExpr.Parameters);
+        }
+
+        #region 基础的与或表达式合并, 无需判断前置条件
+
+        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr)
+        {
+            if (slaveExpr == null) return masterExpr;
+
+            return masterExpr.Compose(slaveExpr, Expression.And);
+        }
+
+        public static Expression<Func<T, bool>> And<T, P>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, P condPara)
+        {
+            if (condPara.IsNull<P>())
+            {
+                return masterExpr;
+            }
+            if (slaveExpr == null) return masterExpr;
+
+            return masterExpr.And(slaveExpr);
+        }
+
+        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, object condPara)
+        {
+            var t = condPara.GetType();
+
+            if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
+            {
+                return masterExpr;
+            }
+            if (slaveExpr == null) return masterExpr;
+
+            return masterExpr.And(slaveExpr);
+        }
+
+        /// <summary>
+        /// 如果传入的excute委托返回true,则执行express的and运算并返回新的express,否则返回原express
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <typeparam name="P"></typeparam>
+        /// <param name="masterExpr"></param>
+        /// <param name="slaveExpr"></param>
+        /// <param name="condPara"></param>
+        /// <param name="excute">express执行条件</param>
+        /// <returns></returns>
+        public static Expression<Func<T, bool>> AndIf<T, P>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, P condPara, Func<P, bool> excute)
+        {
+            if (excute.Invoke(condPara))
+                return masterExpr.And(slaveExpr, condPara);
+
+            if (slaveExpr == null) return masterExpr;
+
+            return masterExpr;
+        }
+
+        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr)
+        {
+            if (slaveExpr == null) return masterExpr;
+            return masterExpr.Compose(slaveExpr, Expression.Or);
+        }
+
+        public static Expression<Func<T, bool>> Or<T, P>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, P condPara)
+        {
+            if (condPara.IsNull<P>())
+            {
+                return masterExpr;
+            }
+
+            if (slaveExpr == null) return masterExpr;
+
+            return masterExpr.Or(slaveExpr);
+        }
+
+        #endregion
+
+        #region 基础的与或表达式合并, 需判断前置条件
+
+        public static Expression<Func<T, bool>> And<C, T>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, C preCondPara, Func<C, bool> preConditionExpr)
+        {
+            return preConditionExpr(preCondPara) ? masterExpr.Compose(slaveExpr, Expression.And) : masterExpr;
+        }
+
+        public static Expression<Func<T, bool>> Or<C, T>(this Expression<Func<T, bool>> masterExpr, Expression<Func<T, bool>> slaveExpr, C preCondPara, Func<C, bool> preConditionExpr)
+        {
+            return preConditionExpr(preCondPara) ? masterExpr.Compose(slaveExpr, Expression.Or) : masterExpr;
+        }
+
+        #endregion
+    }
+}

+ 514 - 0
src/01.Infrastructure/Exam.Infrastructure/Extensions/ObjectExtension.cs

@@ -0,0 +1,514 @@
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using System.Xml.Serialization;
+using Exam.Infrastructure.Attributes;
+
+namespace Exam.Infrastructure.Extensions
+{
+    public static class ObjectExtension
+    {
+        #region public method
+        public static bool TryConvert<T>(this object obj, out T result)
+        {
+            try
+            {
+                var converted = Convert.ChangeType(obj, typeof(T));
+                if (converted is T)
+                {
+                    result = (T)converted;
+                    return true;
+                }
+            }
+            catch
+            {
+
+            }
+
+            result = default(T);
+            return false;
+        }
+
+        public static T ConvertTo<T>(this object obj)
+        {
+            try
+            {
+                var converted = Convert.ChangeType(obj, typeof(T));
+                if (converted is T)
+                {
+                    return (T)converted;
+                }
+            }
+            catch
+            {
+
+            }
+
+            return default(T);
+        }
+
+        /// <summary>
+        /// 对象序列化为字符串
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static string ToJson(this object obj)
+        {
+            var jsonSerializerSettings = new JsonSerializerSettings
+            {
+                NullValueHandling = NullValueHandling.Ignore,
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+            };
+
+            var result = JsonConvert.SerializeObject(obj, jsonSerializerSettings);
+
+            return result;
+        }
+
+        /// <summary>
+        /// 对象序列化为字符串
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static T ToEntity<T>(this string value)
+        {
+            var jsonSerializerSettings = new JsonSerializerSettings
+            {
+                NullValueHandling = NullValueHandling.Ignore,
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+            };
+
+            var result = JsonConvert.DeserializeObject<T>(value, jsonSerializerSettings);
+
+            return result;
+        }
+
+        /// <summary>
+        /// 对象序列化为字符串
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static T ToEntity<T>(this JObject value)
+        {
+            var jsonSerializerSettings = new JsonSerializerSettings
+            {
+                NullValueHandling = NullValueHandling.Ignore,
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+            };
+
+            var result = JsonConvert.DeserializeObject<T>(value.ToString(), jsonSerializerSettings);
+
+            return result;
+        }
+
+        /// <summary>
+        /// 序列化为XML字符串
+        /// </summary>
+        public static string ToXml(this object obj)
+        {
+            using (var stream = new MemoryStream())
+            {
+                var xml = new XmlSerializer(obj.GetType());
+                try
+                {
+                    //序列化对象
+                    xml.Serialize(stream, obj);
+                }
+                catch (InvalidOperationException)
+                {
+                    throw;
+                }
+                stream.Position = 0;
+                using (var streamReader = new StreamReader(stream))
+                {
+                    string xmlString = streamReader.ReadToEnd();
+                    return xmlString;
+                }
+            }
+        }
+
+        /// <summary>
+        /// XML字符串反虚拟化对象
+        /// </summary>
+        public static T XmlToObj<T>(this string xmlString)
+        {
+            try
+            {
+                using (var stringReader = new StringReader(xmlString))
+                {
+                    XmlSerializer xml = new XmlSerializer(typeof(T));
+                    return (T)xml.Deserialize(stringReader);
+                }
+            }
+            catch (Exception)
+            {
+                throw;
+            }
+
+        }
+
+        /// <summary>
+        /// 时间戳转换为时间
+        /// </summary>
+        /// <param name="timeStamp"></param>
+        /// <returns></returns>
+        public static DateTime StampToTime(this string timeStamp)
+        {
+            DateTime dateTimeStart = TimeZone.CurrentTimeZone.ToLocalTime(DateTime.Now);
+            long lTime = long.Parse(timeStamp);
+            TimeSpan toNow = new TimeSpan(lTime);
+            return dateTimeStart.Add(toNow);
+        }
+
+        /// <summary>
+        /// DateTime时间格式转换为Unix时间戳格式
+        /// </summary>
+        /// <param name="time"></param>
+        /// <returns></returns>
+        public static string TimeToStamp(this System.DateTime time)
+        {
+            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
+            var result = (int)(time - startTime).TotalSeconds;
+            return result.ToString();
+        }
+
+        public static bool IsNull<T>(this T value)
+        {
+            if (value != null)
+                return false;
+
+            Type type = typeof(T);
+
+            if (!type.IsValueType)
+                return true; // ref-type
+
+            if (Nullable.GetUnderlyingType(type) != null)
+                return true; // Nullable<T>
+
+            return false; // value-type
+        }
+
+        public static T GetAttribute<T>(this MemberInfo field) where T : Attribute
+        {
+            return Attribute.GetCustomAttribute(field, typeof(T)) as T;
+        }
+
+        public static string GetDescription(this Type type)
+        {
+
+            var attrs = type.GetCustomAttributes();
+
+            foreach (var attr in attrs)
+            {
+                if (attr is DescriptionAttribute)
+                {
+                    return ((DescriptionAttribute)attr).Description;
+                }
+            }
+            return string.Empty;
+        }
+
+        public static string GetDescription(this Type type, string memberName)
+        {
+            MemberInfo memberInfo = type.GetMembers().FirstOrDefault(m => m.Name == memberName);
+
+            if (memberInfo == null) return string.Empty;
+
+            var attribute = GetAttribute<DescriptionAttribute>(memberInfo);
+
+            return attribute != null ? attribute.Description : string.Empty;
+        }
+
+        public static string GetAttrDescription(this object value, string memberName)
+        {
+            MemberInfo memberInfo = value.GetType().GetMembers().FirstOrDefault(m => m.Name == memberName);
+
+            if (memberInfo == null) return string.Empty;
+
+            var attribute = GetAttribute<DescriptionAttribute>(memberInfo);
+
+            return attribute != null ? attribute.Description : string.Empty;
+        }
+
+        public static string GetObjDescription(this object value, string name)
+        {
+            if (value != null)
+            {
+                var properties = value.GetType().GetProperties();
+
+                var property = properties.FirstOrDefault(m => m.Name == name);
+
+                var attrs = property.GetCustomAttributes();
+
+                foreach (var attr in attrs)
+                {
+                    if (attr is DescriptionAttribute)
+                    {
+                        return ((DescriptionAttribute)attr).Description;
+                    }
+                }
+            }
+            return string.Empty;
+        }
+
+        public static string GetObjDescription(this object value)
+        {
+            if (value != null)
+            {
+                var attrs = value.GetType().GetCustomAttributes();
+
+                foreach (var attr in attrs)
+                {
+                    if (attr is DescriptionAttribute)
+                    {
+                        return ((DescriptionAttribute)attr).Description;
+                    }
+                }
+            }
+            return string.Empty;
+        }
+
+        public static string GetTableName(this object value)
+        {
+            if (value != null)
+            {
+                var attrs = value.GetType().GetCustomAttributes();
+
+                foreach (var attr in attrs)
+                {
+                    if (attr is TableAttribute)
+                    {
+                        return ((TableAttribute)attr).Name;
+                    }
+                }
+            }
+            return string.Empty;
+        }
+
+        //public static string GetObjDescription(this object value)
+        //{
+        //    var attrs = value.GetType().GetCustomAttributes();
+
+        //    foreach (var attr in attrs)
+        //    {
+        //        if (attr is DescriptionAttribute)
+        //        {
+        //            return ((DescriptionAttribute)attr).Description;
+        //        }
+        //    }
+        //    return string.Empty;
+        //}
+
+        /// <summary>
+        /// 把简单一个对象转换成属性名-属性值的键值对字典
+        /// 注意:此扩展方法不支持嵌套的复杂对象
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static IDictionary<string, object> ToDictionary(this object obj)
+        {
+            if (obj == null)
+                return new Dictionary<string, object>();
+
+            var dict = new Dictionary<string, object>();
+
+            var props = obj.GetType().GetProperties();
+
+            foreach (var prop in props)
+            {
+                var attributes = prop.GetCustomAttributes<ReflectIgnoreAttribute>();
+
+                if (attributes != null && attributes.Any()) continue;
+
+                if (prop.GetValue(obj) != null)
+                {
+                    dict[prop.Name] = prop.GetValue(obj);
+                }
+            }
+            return dict;
+        }
+
+        /// <summary>
+        /// 把简单一个对象转换成属性名-属性值的键值对字典
+        /// 注意:此扩展方法不支持嵌套的复杂对象
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static IDictionary<TKey, TValue> ToDictionary<TKey, TValue>(this object obj)
+        {
+            if (obj == null)
+                return new Dictionary<TKey, TValue>();
+
+            var dict = new Dictionary<TKey, TValue>();
+
+            var props = obj.GetType().GetProperties();
+
+            foreach (var prop in props)
+            {
+                var attributes = prop.GetCustomAttributes<ReflectIgnoreAttribute>();
+
+                if (attributes != null && attributes.Any()) continue;
+
+                if (prop.GetValue(obj) != null)
+                {
+                    var key = (TKey)Convert.ChangeType(prop.Name, typeof(TKey));
+
+                    if (key != null)
+                    {
+                        dict[key] = (TValue)Convert.ChangeType(prop.GetValue(obj), typeof(TValue));
+                    }
+                }
+            }
+            return dict;
+        }
+        /// <summary>
+        /// 把简单一个对象转换成属性名-属性值的键值对字典
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static IDictionary<string, object> ToDeepDictionary(this object obj)
+        {
+            if (obj == null)
+                return new Dictionary<string, object>();
+
+            var dict = new Dictionary<string, object>();
+
+            Type objType = obj.GetType();
+
+            if (objType.IsGenericType || objType.Equals(typeof(string)))
+            {
+                return dict;
+            }
+
+            var props = obj.GetType().GetProperties();
+
+            foreach (var prop in props)
+            {
+                var attributes = prop.GetCustomAttributes<ReflectIgnoreAttribute>();
+
+                if (attributes != null && attributes.Any()) continue;
+
+                var childObj = prop.GetValue(obj);
+
+                if (childObj != null)
+                {
+                    dict[prop.Name] = childObj.ToDeepDictionary();
+                }
+            }
+            return dict;
+        }
+
+        public static SortedDictionary<string, TValue> ToSortedDictionary<TValue>(this object obj, int sorted = 0, bool isEncode = false)
+        {
+            if (obj == null)
+                return new SortedDictionary<string, TValue>();
+
+            var dict = new SortedDictionary<string, TValue>();
+
+            var props = obj.GetType().GetProperties();
+
+            foreach (var prop in props)
+            {
+                var attributes = prop.GetCustomAttributes<ReflectIgnoreAttribute>();
+
+                if (attributes != null && attributes.Any()) continue;
+
+                if (prop.GetValue(obj) != null)
+                {
+                    dict[Encode(prop.Name, isEncode).ToString()] = (TValue)Convert.ChangeType(Encode(prop.GetValue(obj), isEncode), typeof(TValue));
+                }
+            }
+            return (SortedDictionary<string, TValue>)(sorted == 0 ? dict.OrderBy(x => x.Key) : dict.OrderByDescending(x => x.Key));
+        }
+
+        /// <summary>
+        /// JSON序列化和反序列化实现的深拷贝
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static T DeepClone<T>(this T source)
+        {
+            T newInfo = System.Text.Json.JsonSerializer.Deserialize<T>(System.Text.Json.JsonSerializer.Serialize(source));
+
+            return newInfo;
+        }
+
+        public static List<T> DeepCloneClear<T>(this List<T> source)
+        {
+            var result = source.DeepClone();
+            source.Clear();
+            return result;
+        }
+
+        public static byte[] GetBytes<T>(this T source)
+        {
+            byte[] bytes = null;
+
+            if (source != null)
+            {
+                bytes = Encoding.UTF8.GetBytes(source.ToJson());
+            }
+
+            return bytes;
+        }
+
+        public static T GetEntity<T>(this byte[] bytes)
+        {
+
+            T entity = default(T);
+
+            if (bytes != null)
+            {
+
+                var source = Encoding.UTF8.GetString(bytes);
+
+                entity = source.ToEntity<T>();
+            }
+
+            return entity;
+        }
+
+        public static IDictionary<string, object> ToDictionary(this JObject @object)
+        {
+            var result = @object.ToObject<Dictionary<string, object>>();
+
+            var JObjectKeys = (from r in result
+                               let key = r.Key
+                               let value = r.Value
+                               where value.GetType() == typeof(JObject)
+                               select key).ToList();
+
+            var JArrayKeys = (from r in result
+                              let key = r.Key
+                              let value = r.Value
+                              where value.GetType() == typeof(JArray)
+                              select key).ToList();
+
+            JArrayKeys.ForEach(key => result[key] = ((JArray)result[key]).Values().Select(x => ((JValue)x).Value).ToArray());
+            JObjectKeys.ForEach(key => result[key] = ToDictionary(result[key] as JObject));
+
+            return result;
+        }
+        #endregion
+
+        #region private method
+        private static object Encode(object value, bool isEncode)
+        {
+            if (isEncode)
+            {
+                return HttpUtility.HtmlEncode(value);
+            }
+            return value;
+        }
+        #endregion
+    }
+}

+ 55 - 0
src/01.Infrastructure/Exam.Infrastructure/Extensions/ParameterRebinder.cs

@@ -0,0 +1,55 @@
+using System.Linq.Expressions;
+
+namespace Exam.Infrastructure.Extensions
+{
+    internal class ParameterRebinder : ExpressionVisitor
+    {
+        private Dictionary<ParameterExpression, ParameterExpression> parameterMap;
+
+        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> parameterMap)
+        {
+            this.parameterMap = parameterMap;
+        }
+
+        /// <summary>
+        /// 替换表达式树Lambda传入的参数名称(合并后, 2个表达式需相同的Lambda参数名称)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="slaveExpr">被替换Lambda传入参数的表达式</param>
+        /// <param name="masterExpr">用于替换Lambda传入参数的源表达式</param>
+        /// <returns></returns>
+        internal static Expression ReplaceParameters<T>(Expression<T> slaveExpr, Expression<T> masterExpr)
+        {
+            if (slaveExpr.Parameters.Count() != masterExpr.Parameters.Count())
+                throw new ArgumentException("表达式\"" + slaveExpr.ToString() + "\"与表达式\"" + masterExpr.ToString() + "\"由于参数不对等, 无法进行与或操作");
+
+            // 获取主表达式与从表达式Lambda参数从左至右依顺序的对应关系
+            var parameterMap = masterExpr.Parameters.Select((paraSource, index) => new { paraSource, paraReplace = slaveExpr.Parameters[index] }).ToDictionary(p => p.paraReplace, p => p.paraSource);
+
+            // 根据对应关系替换从表达式的Lambda参数为主表达式Lambda参数
+            return new ParameterRebinder(parameterMap).Visit(slaveExpr.Body);
+        }
+
+        //
+        // 摘要:
+        //     访问 System.Linq.Expressions.ParameterExpression。
+        //
+        // 参数:
+        //   node:
+        //     要访问的表达式。
+        //
+        // 返回结果:
+        //     如果修改了该表达式或任何子表达式,则为修改后的表达式;否则返回原始表达式。
+        protected override Expression VisitParameter(ParameterExpression paraReplace)
+        {
+            ParameterExpression paraSource;
+
+            if (this.parameterMap.TryGetValue(paraReplace, out paraSource))
+            {
+                paraReplace = paraSource;
+            }
+
+            return base.VisitParameter(paraReplace);
+        }
+    }
+}

+ 530 - 0
src/01.Infrastructure/Exam.Infrastructure/Extensions/StringExtension.cs

@@ -0,0 +1,530 @@
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Xml.Serialization;
+using Newtonsoft.Json;
+
+namespace Exam.Infrastructure.Extensions
+{
+    public static class StringExtension
+    {
+        public static bool IsNullOrEmpty(this string source)
+        {
+            return string.IsNullOrEmpty(source);
+        }
+
+        public static bool IsNotNullOrEmpty(this string source)
+        {
+            return !string.IsNullOrEmpty(source);
+        }
+
+        public static bool IsNullOrWhiteSpace(this string source)
+        {
+            return string.IsNullOrWhiteSpace(source);
+        }
+
+        public static bool IsNumericOnly(this string source)
+        {
+            if (string.IsNullOrEmpty(source))
+            {
+                return false;
+            }
+
+            return source.All(Char.IsDigit);
+        }
+
+        /// <summary>
+        /// Ensure that a string is not null
+        /// </summary>
+        /// <param name="source">Input string</param>
+        /// <returns>Result</returns>
+        public static string EnsureNotNull(this string source)
+        {
+            return source ?? String.Empty;
+        }
+
+        public static string EnsureTrim(this string str, params char[] trimChars)
+        {
+            if (str == null)
+            {
+                return null;
+            }
+
+            if (trimChars == null || trimChars.Length == 0)
+            {
+                return str.Trim();
+            }
+
+            return str.Trim(trimChars);
+
+        }
+
+
+        public static bool? ToBoolean(this string source)
+        {
+            if (string.Compare(source, "true", StringComparison.InvariantCultureIgnoreCase) == 0)
+            {
+                return true;
+            }
+
+            if (String.Compare(source, "false", StringComparison.InvariantCultureIgnoreCase) == 0)
+            {
+                return false;
+            }
+
+            return null;
+        }
+
+
+        public static float? ToFloat(this string source)
+        {
+            if (float.TryParse(source, out var result))
+            {
+                return result;
+            }
+
+            return null;
+        }
+
+
+        public static int? ToInt32(this string source)
+        {
+            if (int.TryParse(source, out var result))
+            {
+                return result;
+            }
+
+            return null;
+        }
+
+        public static DateTime? ToDateTime(this string source)
+        {
+            if (DateTime.TryParse(source, out var result))
+            {
+                return result;
+            }
+
+            return null;
+        }
+
+        public static DateTime? ToDateTime(this string source, string format)
+        {
+            string[] formats = { format };
+
+            if (DateTime.TryParseExact(source,
+                                       formats,
+                                       CultureInfo.InvariantCulture,
+                                       DateTimeStyles.None,
+                                       out var result))
+            {
+                return result;
+            }
+
+            return null;
+        }
+
+        public static DateTime ToNotNullDateTime(this string source)
+        {
+            if (DateTime.TryParse(source, out var result))
+            {
+                return result;
+            }
+
+            return DateTime.MinValue;
+        }
+
+        /// <summary>
+        /// 将特定字符分隔的字符串转换为INT数组
+        /// </summary>
+        public static int[] ToIntArray(this string strObj, char splitChar)
+        {
+            if (strObj.Length == 0)
+            {
+                return new int[] { };
+            }
+
+            var strArray = strObj.Split(new char[] { splitChar }, StringSplitOptions.RemoveEmptyEntries);
+            var intArray = new int[strArray.Length];
+
+            for (var i = 0; i < strArray.Length; i++)
+                intArray[i] = int.Parse(strArray[i]);
+
+            return intArray;
+        }
+
+        /// <summary>
+        /// 获取字符串左边指定的字节个数
+        /// </summary>
+        public static string Left(this string strObj, int leftCount, string appendString = "")
+        {
+            if (string.IsNullOrEmpty(strObj))
+                return string.Empty;
+            int i = 0, j = 0;
+            foreach (char c in strObj)
+            {
+                if (c > 127)
+                {
+                    i += 2;
+                }
+                else
+                {
+                    i++;
+                }
+
+                if (i > leftCount)
+                {
+                    return strObj.Substring(0, j) + appendString;
+                }
+
+                j++;
+            }
+
+            return strObj;
+        }
+
+        /// <summary>
+        /// 字符串真实长度 如:一个汉字为两个字节
+        /// </summary>
+        public static int GetSize(this string strObj)
+        {
+            return Encoding.Default.GetBytes(strObj).Length;
+        }
+
+        /// <summary>
+        /// 序列化为报文内容
+        /// </summary>
+        /// <param name="xml">以<packet>标签开始的xml内容</param>
+        /// <returns></returns>
+        public static T FromXml<T>(this string xml)
+        {
+            int index;
+            if (xml.Trim().StartsWith("<?xml") && (index = xml.IndexOf("?>")) != -1)
+            {
+                xml = xml.Substring(index + 2).Trim('\r', '\n', ' ');
+            }
+            try
+            {
+                using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
+                {
+                    XmlSerializer serializer = new XmlSerializer(typeof(T));
+                    try
+                    {
+                        return (T)serializer.Deserialize(stream);
+                    }
+                    catch
+                    {
+                        return default(T);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"反序列化对象信息异常:{ex.Message}", ex);
+            }
+        }
+
+
+        /// <summary>
+        /// JSON字符反序列化为对象
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static T JsonToObj<T>(this string str)
+        {
+            try
+            {
+                return JsonConvert.DeserializeObject<T>(str);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"无法将字符串 {str} 反序列化为 {typeof(T)} 类型的对象", ex);
+            }
+        }
+
+
+        public static bool TryJsonToObj<T>(this string str, out T result)
+        {
+            try
+            {
+                result = JsonConvert.DeserializeObject<T>(str);
+
+                return true;
+            }
+            catch
+            {
+                result = default(T);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 将首字母转换为小写字符
+        /// </summary>
+        /// <param name="content"></param>
+        /// <returns></returns>
+        public static string ToTitileLower(this string content)
+        {
+            if (string.IsNullOrEmpty(content))
+                return content;
+
+            return content.Substring(0, 1).ToLower() + content.Substring(1);
+        }
+
+        /// <summary>
+        /// 将首字母转换为大写字符
+        /// </summary>
+        /// <param name="content"></param>
+        /// <returns></returns>
+        public static string ToTitileUpper(this string content)
+        {
+            if (string.IsNullOrEmpty(content))
+                return content;
+
+            return content.Substring(0, 1).ToUpper() + content.Substring(1);
+        }
+
+        public static string EncryptStr(this string content, string desKey)
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(desKey))
+                {
+                    return content;
+                }
+                DESCryptoServiceProvider provider = new DESCryptoServiceProvider
+                {
+                    Mode = CipherMode.ECB
+                };
+                byte[] buffer = new byte[8];
+                if (desKey.Length < 8)
+                {
+                    byte[] buffer2 = Encoding.UTF8.GetBytes(desKey);
+                    for (int i = 0; i < buffer.Length; i++)
+                    {
+                        if (buffer2.Length > i)
+                        {
+                            buffer[i] = buffer2[i];
+                        }
+                        else
+                        {
+                            buffer[i] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    buffer = Encoding.UTF8.GetBytes(desKey.Substring(0, 8));
+                }
+                provider.Key = buffer;
+                provider.IV = provider.Key;
+                byte[] bytes = Encoding.UTF8.GetBytes(content);
+                MemoryStream stream = new MemoryStream();
+                CryptoStream stream2 = new CryptoStream(stream, provider.CreateEncryptor(), CryptoStreamMode.Write);
+                stream2.Write(bytes, 0, bytes.Length);
+                stream2.FlushFinalBlock();
+                StringBuilder builder = new StringBuilder();
+                foreach (byte num2 in stream.ToArray())
+                {
+                    builder.Append(num2.ToString());
+                    builder.Append("_");
+                }
+                if (builder.Length > 0)
+                {
+                    builder = builder.Remove(builder.Length - 1, 1);
+                }
+                return builder.ToString();
+            }
+            catch (Exception)
+            {
+                return content;
+            }
+        }
+
+
+        /// <summary>
+        /// 尝试分隔字符串,分隔失败时返回空集合
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="separato"></param>
+        /// <param name="removeEmptyItems"></param>
+        /// <returns></returns>
+        public static List<string> TrySplit(this string str, bool removeEmptyItems = false, params char[] separato)
+        {
+            if (str.IsNullOrEmpty())
+                return new List<string>();
+
+            return str.Split(separato).Where(x => removeEmptyItems ? x.IsNotNullOrEmpty() : true).ToList();
+        }
+
+        /// <summary>
+        /// 消除数据库中直接存储的数组格式
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static string DislodgeArray(this string str)
+        {
+            var value = str.Replace("[", "");
+            value = value.Replace("]", "");
+            value = value.Replace("\"", "");
+            return value;
+        }
+
+        /// <summary>
+        /// 处理application/x-www-form-urlencoded请求参数
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static IEnumerable<KeyValuePair<string?, string?>> ToKeyValuePairs(this string str)
+        {
+            var result = new List<KeyValuePair<string?, string?>>();
+            if (str.Contains('&') && str.Contains('='))
+            {
+                var keyValues = str.Split('&');
+
+                foreach (var item in keyValues)
+                {
+                    var keyValuePairs = item.Split('=');
+                    result.Add(new KeyValuePair<string?, string?>(keyValuePairs[0], keyValuePairs[1]));
+                }
+            }
+
+            if (str.Contains(',') && str.Contains(':'))
+            {
+                var dictionary = str.ToEntity<Dictionary<string, string>>();
+
+                foreach (var item in dictionary)
+                {
+                    //var keyValuePairs = item.Split(':');
+                    result.Add(new KeyValuePair<string?, string?>(item.Key, item.Value));
+                }
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// 字符串转换为字典
+        /// </summary>
+        /// <typeparam name="TKey"></typeparam>
+        /// <typeparam name="TValue"></typeparam>
+        /// <param name="content"></param>
+        /// <param name="fisrt"></param>
+        /// <param name="second"></param>
+        /// <returns></returns>
+        public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this string content, char fisrt, char second)
+        {
+            var result = new Dictionary<TKey, TValue>();
+
+            var firstLayer = content.Split(fisrt);
+
+            foreach (var item in firstLayer)
+            {
+                var secondLayer = item.Split(second);
+
+                if (secondLayer.Count() >= 2)
+                {
+                    var key = (TKey)Convert.ChangeType(secondLayer[0], typeof(TKey));
+
+                    if (!result.ContainsKey(key))
+                    {
+                        var value = (TValue)Convert.ChangeType(secondLayer[1], typeof(TValue));
+
+                        result.Add(key, value);
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// 转换为AscII字符串
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static string ToAscIIString(this string str)
+        {
+            // ASCII码本身并不支持中国的汉字,那么我们需要将汉字转换成对应的16进制码,然后取出对应的ASCII16进制码组成汉字编码。
+
+
+            //这里我们将采用2字节一个汉字的方法来取出汉字的16进制码
+
+            byte[] textbuf = Encoding.GetEncoding("GB2312").GetBytes(str);
+
+            //将字节转化成汉字
+
+            var textStr = Encoding.GetEncoding("GB2312").GetString(textbuf);
+
+            return textStr;
+        }
+
+        public static IList<T> ToExtensionList<T>(this IList<string> source)
+        {
+            var result = new List<T>();
+
+            try
+            {
+                foreach (var item in source)
+                {
+                    result.Add((T)Convert.ChangeType(item, typeof(T)));
+                }
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// 获取字符串中的中文
+        /// </summary>
+        public static string GetChineseWord(this string source)
+        {
+            string expression = @"[\u4E00-\u9FFF]+";
+
+            MatchCollection Matches = Regex.Matches(source, expression, RegexOptions.IgnoreCase);
+            StringBuilder sb = new StringBuilder();
+
+            foreach (Match match in Matches)
+            {
+                sb.Append(match.Value);
+            }
+
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 处理超长字长
+        /// </summary>
+        /// <typeparam name="TResult"></typeparam>
+        /// <typeparam name="TParams"></typeparam>
+        /// <param name="content"></param>
+        /// <param name="maxinum"></param>
+        /// <param name="resolve"></param>
+        /// <returns></returns>
+        public static List<TResult> ResolveMaxinumString<TResult>(this string content, int maxinum, Func<string, int, TResult> resolve)
+        {
+            var results = new List<TResult>();
+            if (content.Length > maxinum)
+            {
+                var repeat = content.Length / maxinum;
+
+                repeat += content.Length % maxinum != 0 ? 0 : 1;
+
+                for (var i = 0; i < repeat; i++)
+                {
+                    results.Add(resolve(content.Substring(i * maxinum, maxinum), i));
+                }
+            }
+            else
+            {
+                results.Add(resolve(content, 0));
+            }
+            return results;
+        }
+    }
+}

+ 244 - 0
src/01.Infrastructure/Exam.Infrastructure/Utilities/BaseAssemblyUtility.cs

@@ -0,0 +1,244 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Exam.Infrastructure.Utilities
+{
+    public class BaseAssemblyUtility
+    {
+        public static string ProjectPrefix = @"Exam";
+
+        public static string API = string.Format(@"^{0}\.(.*\.)?Api$", ProjectPrefix);
+
+        public static string CLIENT = @"^PlanReady\.(.*\.)?Client";
+
+        public static string EXTENSIONS = @"^PlanReady\.Extensions\..*$";
+
+        public static string REPOSITORY = string.Format(@"^{0}\.(.*\.)?Repository", ProjectPrefix);
+
+        public static string DOMAIN = string.Format(@"^{0}\.(.*\.)?Domain", ProjectPrefix);
+
+        public static string CONSTRACT = string.Format(@"^{0}\.(.*\.)?Contract", ProjectPrefix);
+
+        public static string APISERVICE = string.Format(@"^{0}\.(.*\.)?ApiService", ProjectPrefix);
+
+        public static string VITRUALDOMAIN = string.Format(@"^{0}\.(.*\.)?Domain", ProjectPrefix);
+
+        public static string VITRUALCONSTRACT = string.Format(@"^{0}\.(.*\.)?Contract", ProjectPrefix);
+
+        public static string VITRUALAPISERVICE = string.Format(@"^{0}\.(.*\.)?ApiService", ProjectPrefix);
+
+        public static string VITRUALREPOSITORY = string.Format(@"^{0}\.(.*\.)?Repository", ProjectPrefix);
+
+        public static string VITRUALSIGNATURE = string.Format(@"^{0}\.(.*\.)?Signature", ProjectPrefix);
+
+        public static string VITRUALENTITY = string.Format(@"{0}", ProjectPrefix);
+
+        public static string VITRUALSHARE = string.Format(@"^{0}\.(.*\.)?Share", ProjectPrefix);
+
+        public static string VITRUALAPPLICATION = string.Format(@"^{0}\.(.*\.)?Application", ProjectPrefix);
+
+        public static string ALL = "*.dll";
+
+
+
+        static BaseAssemblyUtility()
+        {
+            //Hashtable = new Hashtable();
+        }
+
+        public static IServiceProvider Provider { get; set; }
+
+        private static string[] FinderFilter
+        {
+            get
+            {
+                return new string[] { API, CLIENT, EXTENSIONS };
+            }
+        }
+        private static string[] ModuleFilter
+        {
+            get
+            {
+                return new string[] { @"^PlanReady\.(.*\.)?Services$", @"^PlanReady\.(.*\.)\.Data$" };
+            }
+        }
+
+        public static string TraceId
+        {
+            get
+            {
+                try
+                {
+                    var httpContextAccessor = Provider.GetService<IHttpContextAccessor>();
+
+                    if (httpContextAccessor == null) return null;
+
+                    var id = httpContextAccessor.HttpContext?.TraceIdentifier ?? "defaultId";
+
+                    return id;
+                }
+                catch
+                {
+                    return null;
+                }
+            }
+        }
+
+        //public static Hashtable Hashtable { get; set; }
+
+        public static List<Assembly> GetAssembly(string path)
+        {
+            //dynamic type = (new Program()).GetType();
+            string currentDirectory = Path.GetDirectoryName(path);
+            var files = Directory.GetFiles(currentDirectory, ALL);
+            var assemblys = new List<Assembly>();
+
+            foreach (var file in files)
+            {
+                assemblys.Add(Assembly.LoadFrom(file));
+            }
+
+            return assemblys;
+        }
+
+        public static List<Assembly> CreateModules(List<Assembly> assemblies)
+        {
+            List<Assembly> modules = new List<Assembly>();
+            foreach (var filter in ModuleFilter)
+            {
+                CreateModulesByFilter(assemblies, filter);
+            }
+            return modules;
+        }
+
+        public static List<Assembly> CreateModulesByFilter(List<Assembly> assemblies, string filter)
+        {
+            List<Assembly> modules = new List<Assembly>();
+            modules.AddRange(
+                assemblies.Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.CodeBase), filter)));
+            return modules;
+        }
+
+        public static Dictionary<string, List<Assembly>> CreateFinders(List<Assembly> assemblies)
+        {
+            Dictionary<string, List<Assembly>> finders = new Dictionary<string, List<Assembly>>();
+            foreach (var filter in FinderFilter)
+            {
+                if (!finders.ContainsKey(filter))
+                {
+                    finders[filter] = new List<Assembly>();
+                }
+                finders[filter]
+                    .AddRange(assemblies.Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.CodeBase), filter)));
+            }
+            return finders;
+        }
+
+
+
+        public static Tuple<Dictionary<string, List<Assembly>>, Assembly[]> ScannerAssemblies(string path)
+        {
+            var assemblies = GetAssembly(path);
+
+            var finders = CreateFinders(assemblies);
+
+            var modules = CreateModules(assemblies);
+
+            return new Tuple<Dictionary<string, List<Assembly>>, Assembly[]>(finders, modules.ToArray());
+        }
+
+        public static List<Type> GetTypeToRegister<TEntity>(List<Assembly> assemblies, string filter)
+        {
+            var types = new List<Type>();
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                types.AddRange(currentAssembly.GetTypes().Where(q => q.GetInterface(typeof(TEntity).FullName) != null));
+
+            }
+
+            return types;
+        }
+
+        public static void RegisterInstance(IServiceCollection container, List<Assembly> assemblies, List<Type> interfaces, string filter)
+        {
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                foreach (var item in interfaces)
+                {
+                    var types = currentAssembly.GetTypes().Where(m => m.GetInterfaces().Contains(item) && !m.IsInterface).ToList();
+
+                    foreach (var type in types)
+                    {
+                        container.AddSingleton(type.GetInterfaces().FirstOrDefault(m => m != item), type);
+                        //instatnces.Add((TEntity)currentAssembly.CreateInstance(type.FullName));
+                    }
+
+                }
+            }
+        }
+
+        public static void RegisterAddTransientInstance(IServiceCollection container, List<Assembly> assemblies, List<Type> interfaces, string filter)
+        {
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                foreach (var item in interfaces)
+                {
+                    var types = currentAssembly.GetTypes().Where(m => m.GetInterfaces().Contains(item) && !m.IsInterface).ToList();
+
+                    foreach (var type in types)
+                    {
+                        container.AddTransient(type.GetInterfaces().FirstOrDefault(m => m != item), type);
+                        //instatnces.Add((TEntity)currentAssembly.CreateInstance(type.FullName));
+                    }
+
+                }
+            }
+        }
+
+        public static List<TEntity> CreateInstance<TEntity>(string filter)
+        {
+            var instatnces = new List<TEntity>();
+
+            instatnces = Provider.GetServices<TEntity>().Where(item => Regex.IsMatch(Path.GetFileNameWithoutExtension(item.GetType().FullName), filter)).ToList();
+
+            return instatnces;
+        }
+
+        public static List<TEntity> CreateInstance<TEntity>(List<Assembly> assemblies, string filter)
+        {
+            var instatnces = new List<TEntity>();
+
+            var currentDomainAssemblies = CreateModulesByFilter(assemblies, filter);
+
+            foreach (var currentAssembly in currentDomainAssemblies)
+            {
+                var types = currentAssembly.GetTypes().Where(q => q.GetInterface(typeof(TEntity).FullName) != null);
+
+                foreach (var type in types)
+                {
+                    instatnces.Add((TEntity)currentAssembly.CreateInstance(type.FullName));
+                }
+            }
+
+            return instatnces;
+        }
+    }
+}

+ 7 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Class1.cs

@@ -0,0 +1,7 @@
+namespace Exam.Insfrastructure.Service
+{
+    public class Class1
+    {
+
+    }
+}

+ 9 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Entitys/StatusActionRequest.cs

@@ -0,0 +1,9 @@
+using Exam.Infrastructure.Data.Entity;
+using System.ComponentModel;
+
+namespace Exam.Insfrastructure.Service.Entitys
+{
+    public class StatusActionRequest:ActionRequest
+    {
+    }
+}

+ 22 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Exam.Insfrastructure.Service.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FluentValidation" Version="11.10.0" />
+    <PackageReference Include="Mapster" Version="7.4.0" />
+    <PackageReference Include="XF.Domain" Version="1.0.11" />
+    <PackageReference Include="XF.Domain.Repository" Version="1.0.8" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Exam.Infrastructure.Data\Exam.Infrastructure.Data.csproj" />
+    <ProjectReference Include="..\Exam.Infrastructure.Validation\Exam.Infrastructure.Validation.csproj" />
+    <ProjectReference Include="..\Exam.Infrastructure\Exam.Infrastructure.csproj" />
+  </ItemGroup>
+
+</Project>

+ 79 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Extensions/ApiServiceExtension.cs

@@ -0,0 +1,79 @@
+using Exam.Infrastructure.Data.Entity;
+using XF.Domain.Entities;
+using XF.Domain.Repository;
+
+namespace Exam.Insfrastructure.Service.Extensions
+{
+    public static class ApiServiceExtension
+    {
+        public static void ToInsert<T,TActionRequest>(this T entity, TActionRequest actionRequest) where T : class, IEntity<string>, new()
+        where TActionRequest : ActionRequest
+        {
+            entity.Id = Guid.NewGuid().ToString();
+
+            if (entity is CreationEntity)
+            {
+                var creationEntity = entity as CreationEntity;
+                if (creationEntity != null)
+                {
+                    creationEntity.CreatorId = actionRequest.UserId;
+                    creationEntity.CreatorOrgId = actionRequest.OrgId;
+                    creationEntity.CreatorOrgLevel = actionRequest.OrgLevel;
+                    creationEntity.CreatorOrgName = actionRequest.OrgName;
+                    creationEntity.CreatorName = actionRequest.UserName;
+                    creationEntity.CreationTime = DateTime.Now;
+
+                    entity = creationEntity as T ?? entity;
+                }
+
+            }
+        }
+
+        public static void ToInsert<T, TActionRequest>(this List<T> entities,List<TActionRequest> actionRequests) where T : class, IEntity<string>, new()
+        where TActionRequest : ActionRequest
+        {
+            foreach(var entity in entities)
+            {
+                var actionRequest = actionRequests.FirstOrDefault(x => x.Id == entity.Id);
+                if (actionRequest != null)
+                {
+                    entity.ToInsert(actionRequest);
+                }
+            }
+        }
+
+        public static void ToUpdate<T, TActionRequest>(this T entity, TActionRequest actionRequest) where T : class, IEntity<string>, new()
+        where TActionRequest : ActionRequest
+        {
+            if(entity is FullStateEntity)
+            {
+                var fullStateEntity = entity as FullStateEntity;
+                if (fullStateEntity != null)
+                {
+                    fullStateEntity.CreatorId = actionRequest.UserId;
+                    fullStateEntity.CreatorOrgId = actionRequest.OrgId;
+                    fullStateEntity.CreatorOrgLevel = actionRequest.OrgLevel;
+                    fullStateEntity.CreatorOrgName = actionRequest.OrgName;
+                    fullStateEntity.CreatorName = actionRequest.UserName;
+                    fullStateEntity.LastModificationTime = DateTime.Now;
+
+                    entity = fullStateEntity as T ?? entity;
+                }
+                
+            }          
+        }
+
+        public static void ToUpdate<T, TActionRequest>(this List<T> entities, List<TActionRequest> actionRequests) where T : class, IEntity<string>, new()
+       where TActionRequest : ActionRequest
+        {
+            foreach (var entity in entities)
+            {
+                var actionRequest = actionRequests.FirstOrDefault(x => x.Id == entity.Id);
+                if (actionRequest != null)
+                {
+                    entity.ToUpdate(actionRequest);
+                }
+            }
+        }
+    }
+}

+ 26 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IApiService.cs

@@ -0,0 +1,26 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using Exam.Insfrastructure.Service.Entitys;
+using FluentValidation;
+using XF.Domain.Dependency;
+using XF.Domain.Entities;
+
+namespace Exam.Insfrastructure.Service.Interface
+{
+    public interface IApiService<T,TEntity>: IDomainService where T : IActionRequest
+        where TEntity : class, IEntity<string>, new()
+    {
+        Task AddAsync(T actionRequest,CancellationToken cancellationToken);
+
+        Task AddAsync(List<T> actionRequests, CancellationToken cancellationToken);
+
+        Task UpdateAsync(T actionRequest, CancellationToken cancellationToken);
+
+        Task UpdateAsync(List<T> actionRequests, CancellationToken cancellationToken);
+
+        Task DeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken);
+
+        Task UpdateStatus(List<StatusActionRequest> statusActionRequests, CancellationToken cancellationToken);
+
+    }
+}

+ 6 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IDomainService.cs

@@ -0,0 +1,6 @@
+namespace Exam.Insfrastructure.Service.Interface
+{
+    public interface IDomainService
+    {
+    }
+}

+ 60 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IExamRepository.cs

@@ -0,0 +1,60 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Entitys;
+using FluentValidation;
+using Mapster;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Repository;
+
+namespace Exam.Insfrastructure.Service.Interface
+{
+    public interface IExamRepository<TEntity, TDBContext> :IRepository<TEntity> where TEntity : class, IEntity<string>, IHasCreationTime, IDataPermission, new()
+    where TDBContext: SugarUnitOfWork, new()
+    {
+        public ISugarUnitOfWork<TDBContext> UOW { get; }
+
+        public AbstractValidator<TEntity> Validator { get; set; }
+
+        /// <summary>
+        /// 单表新增
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task AddWithValidateAsync(TEntity entity, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 单表批量新增
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task AddWithValidateAsync(List<TEntity> entities, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 单表删除
+        /// </summary>
+        /// <param name="entityQueryRequest"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task DeleteWithValidateAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 单表修改
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task UpdateWithValidateAsync(TEntity entity, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 单表修改
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task UpdateWithValidateAsync(List<TEntity> entities, CancellationToken cancellationToken);
+
+        
+    }
+}

+ 17 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IQueryService.cs

@@ -0,0 +1,17 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using XF.Domain.Dependency;
+
+namespace Exam.Insfrastructure.Service.Interface
+{
+    public interface IQueryService<TView,TActionRequest,TQueryRequest>: IDomainService where TView : IViewResponse
+        where TQueryRequest:IQueryRequest
+        where TActionRequest:IActionRequest
+    {
+        Task<(int, List<TView>)> GetListAsync(TQueryRequest queryRequest);
+
+        Task<PageViewResponse<TView>> GetPagedListAsync(TQueryRequest queryRequest);
+
+        Task<TActionRequest> GetAsync(EntityQueryRequest entityQueryRequest);
+    }
+}

+ 124 - 0
src/01.Infrastructure/Exam.Insfrastructure.Service/Service/ApiService.cs

@@ -0,0 +1,124 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Entitys;
+using Exam.Insfrastructure.Service.Extensions;
+using Exam.Insfrastructure.Service.Interface;
+using FluentValidation;
+using Mapster;
+using SqlSugar;
+using XF.Domain.Entities;
+
+namespace Exam.Insfrastructure.Service.Service
+{
+    public class ApiService<T, TActionRequest, TDBContext> : IApiService<TActionRequest,T> where TActionRequest : ActionRequest
+        where T : class, IEntity<string>, IHasCreationTime, IDataPermission, new()
+        where TDBContext : SugarUnitOfWork, new()
+    {
+        #region ctor
+        private readonly IExamRepository<T, TDBContext> _repository;
+
+        private AbstractValidator<T> _validator;
+        public ApiService(IExamRepository<T, TDBContext> repository)
+        {
+            _repository = repository;
+            _validator = _repository.Validator;
+        } 
+        #endregion
+
+        #region public method
+        /// <summary>
+        /// 单表新增
+        /// </summary>
+        /// <param name="actionRequest"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public virtual async Task AddAsync(TActionRequest actionRequest, CancellationToken cancellationToken)
+        {
+            var entity = actionRequest.Adapt<T>();
+
+            entity.ToInsert(actionRequest);
+
+
+            await _repository.AddWithValidateAsync(entity, cancellationToken);
+        }
+
+        /// <summary>
+        /// 单表批量新增
+        /// </summary>
+        /// <param name="actionRequests"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public virtual async Task AddAsync(List<TActionRequest> actionRequests, CancellationToken cancellationToken)
+        {
+            var entities = actionRequests.Adapt<List<T>>();
+
+            entities.ToInsert(actionRequests);
+
+            await _repository.AddWithValidateAsync(entities, cancellationToken);
+        }
+
+        /// <summary>
+        /// 单表删除
+        /// </summary>
+        /// <param name="entityQueryRequest"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public virtual async Task DeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
+        {           
+            await _repository.DeleteWithValidateAsync(entityQueryRequest,cancellationToken);
+        }
+
+        /// <summary>
+        /// 单表修改
+        /// </summary>
+        /// <param name="actionRequest"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public virtual async Task UpdateAsync(TActionRequest actionRequest, CancellationToken cancellationToken)
+        {
+            var entity = await _repository.GetAsync(actionRequest.Id);
+
+            entity = actionRequest.Adapt<TActionRequest, T>(entity);
+
+            entity.ToUpdate(actionRequest);
+
+            await _repository.UpdateWithValidateAsync(entity, cancellationToken);
+        }
+
+        /// <summary>
+        /// 单表修改
+        /// </summary>
+        /// <param name="actionRequests"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public virtual async Task UpdateAsync(List<TActionRequest> actionRequests, CancellationToken cancellationToken)
+        {
+            var ids = actionRequests.Select(x => x.Id).ToList();
+            var entities = await _repository.QueryAsync(x => ids.Contains(x.Id));
+
+            entities = actionRequests.Adapt<List<TActionRequest>, List<T>>(entities);
+
+            entities.ToUpdate(actionRequests);
+
+            await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+        }
+
+        /// <summary>
+        /// 修改状态
+        /// </summary>
+        /// <param name="statusActionRequests"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdateStatus(List<StatusActionRequest> statusActionRequests, CancellationToken cancellationToken)
+        {
+            var ids = statusActionRequests.Select(x => x.Id).ToList();
+            var entities = await _repository.QueryAsync(x => ids.Contains(x.Id));
+
+            entities = statusActionRequests.Adapt<List<StatusActionRequest>, List<T>>(entities);
+
+            entities.ToUpdate(statusActionRequests);
+
+            await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+        }
+        #endregion
+    }
+}

+ 2 - 2
src/Hotline.Api/config/appsettings.Development.json

@@ -71,14 +71,14 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
     //"Hotline1": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
   },
   "Cache": {
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo123!$!$",
-    "Database": 3 //test:3, dev:5
+    "Database": 5 //test:3, dev:5
   },
   "Swagger": true,
   "AccLog": false,

+ 12 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IExamManageService.cs

@@ -0,0 +1,12 @@
+using Exam.ExamManages;
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Requests.Exam;
+
+namespace Hotline.Application.Exam.Interface.ExamManages
+{
+    public interface IExamManageService:IQueryService<ExamManageViewResponse,ExamManageDto,ExamManagePagedRequest>,IApiService<ExamManageDto,ExamManage>
+    {
+    }
+}

+ 12 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IExamTagService.cs

@@ -0,0 +1,12 @@
+using Exam.ExamManages;
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.Dtos.TestPapers;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IExamTagService:IQueryService<ExamTagViewResponse,ExamTagDto,ExamTagRequest>,IApiService<ExamTagDto,ExamTag>
+    {
+    }
+}

+ 12 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IExtractRuleService.cs

@@ -0,0 +1,12 @@
+using Exam.ExamManages;
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Dtos.TestPapers;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IExtractRuleService:IQueryService<ExtractRuleViewResponse,ExtractRuleDto,ExtractRulePagedRequest>,IApiService<ExtractRuleDto,ExtractRule>
+    {
+    }
+}

+ 11 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IUnExamUserService.cs

@@ -0,0 +1,11 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IUnExamUserService:IQueryService<UnExamUserViewResponse,UserExamDto,UnExamUserReportPagedRequest>
+    {
+    }
+}

+ 11 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IUserExamResultService.cs

@@ -0,0 +1,11 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IUserExamResultService:IQueryService<UserExamResultViewResponse,UserExamDto,UserExamResultReportPagedRequest>
+    {
+    }
+}

+ 28 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs

@@ -0,0 +1,28 @@
+using Exam.ExamManages;
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.Dtos.ExamManage;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IUserExamService:IQueryService<UserExamResultViewResponse,UserExamDto,UserExamPagedRequest>,IApiService<UserExamDto,UserExam>
+    {
+        /// <summary>
+        /// 交卷
+        /// </summary>
+        /// <param name="submitExamDto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task SubmitAsync(SubmitExamDto submitExamDto,CancellationToken cancellationToken);
+        
+        /// <summary>
+        /// 阅卷
+        /// </summary>
+        /// <param name="gradingExtamItemDto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task GradingAsync(GradingExtamItemDto gradingExtamItemDto, CancellationToken cancellationToken);
+    }
+}

+ 12 - 0
src/Hotline.Application/Exam/Interface/Practices/IPracticeService.cs

@@ -0,0 +1,12 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Practices;
+using Exam.Share;
+using Exam.Share.ViewResponses.Practices;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.Practices
+{
+    public interface IPracticeService:IQueryService<PracticeViewResponse, PracticeDto,PracticePagedRequest>,IApiService<PracticeDto,Practice>
+    {
+    }
+}

+ 20 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionAnswerService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 试题参考答案服务接口
+    /// </summary>
+    [Description("试题参考答案服务接口")]
+    public interface IQuestionAnswerService:IQueryService<QuestionAnswerViewResponse,QuestionAnswerDto,QuestionAnswerPagedRequest>,IApiService<QuestionAnswerDto,QuestionAnswer>
+    {
+    }
+}
+
+

+ 20 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionKnowladgeService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 关联知识服务接口
+    /// </summary>
+    [Description("关联知识服务接口")]
+    public interface IQuestionKnowladgeService:IQueryService<QuestionKnowladgeViewResponse,QuestionKnowladgeDto,QuestionKnowladgePagedRequest>,IApiService<QuestionKnowladgeDto,QuestionKnowladge>
+    {
+    }
+}
+
+

+ 20 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionOptionsService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 试题选项服务接口
+    /// </summary>
+    [Description("试题选项服务接口")]
+    public interface IQuestionOptionsService:IQueryService<QuestionOptionsViewResponse,QuestionOptionsDto,QuestionOptionsPagedRequest>,IApiService<QuestionOptionsDto,QuestionOptions>
+    {
+    }
+}
+
+

+ 12 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionService.cs

@@ -0,0 +1,12 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share.ViewResponses.Question;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+
+namespace Exam.Application.Interface.Questions
+{
+    public interface IQuestionService:IQueryService<QuestionViewResponse,QuestionDto,QuestionPagedRequest>,IApiService<QuestionDto,Question>
+    {
+    }
+}

+ 20 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionSourcewareService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 关联课件服务接口
+    /// </summary>
+    [Description("关联课件服务接口")]
+    public interface IQuestionSourcewareService:IQueryService<QuestionSourcewareViewResponse,QuestionSourcewareDto,QuestionSourcewarePagedRequest>,IApiService<QuestionSourcewareDto,QuestionSourceware>
+    {
+    }
+}
+
+

+ 20 - 0
src/Hotline.Application/Exam/Interface/Questions/IQuestionTagService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 试题标签服务接口
+    /// </summary>
+    [Description("试题标签服务接口")]
+    public interface IQuestionTagService:IQueryService<QuestionTagViewResponse,QuestionTagDto,QuestionTagPagedRequest>,IApiService<QuestionTagDto,QuestionTag>
+    {
+    }
+}
+
+

+ 20 - 0
src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareCategoryService.cs

@@ -0,0 +1,20 @@
+
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.Dtos.Sourcewares;
+using Exam.Share.ViewResponses.Sourceware;
+using Exam.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using System.ComponentModel;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 课件类型服务接口
+    /// </summary>
+    [Description("课件类型服务接口")]
+    public interface ISourcewareCategoryService:IQueryService<SourcewareCategoryViewResponse,SourcewareCategoryDto,SourcewareCategoryRequest>,IApiService<SourcewareCategoryDto,SourcewareCategory>
+    {
+    }
+}
+
+

+ 19 - 0
src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareService.cs

@@ -0,0 +1,19 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.Dtos.Sourcewares;
+using Exam.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using System.ComponentModel;
+
+namespace Exam.Application.Interface.Sourcewares
+{
+    /// <summary>
+    /// 课件服务接口
+    /// </summary>
+    [Description("课件服务接口")]
+    public interface ISourcewareService:IQueryService<SourcewareViewResponse,SourcewareDto,SourcewarePagedRequest>,IApiService<SourcewareDto,Sourceware>
+    {
+    }
+}
+
+

+ 12 - 0
src/Hotline.Application/Exam/Interface/TestPapers/ITestPaperService.cs

@@ -0,0 +1,12 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.ViewResponses.TestPaper;
+using Exam.TestPapers;
+using Hotline.Share.Dtos.TestPapers;
+using Hotline.Share.Requests.Exam;
+
+namespace Exam.Application.Interface.TestPapers
+{
+    public interface ITestPaperService:IQueryService<TestPaperViewResponse,TestPaperDto,TestPaperPagedRequest>,IApiService<TestPaperDto,TestPaper>
+    {
+    }
+}

+ 12 - 0
src/Hotline.Application/Exam/Interface/Train/ITrainPlanService.cs

@@ -0,0 +1,12 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share;
+using Exam.Share.ViewResponses.Train;
+using Exam.Trains;
+using Hotline.Share.Requests.Train;
+
+namespace Exam.Application.Interface.Train
+{
+    public interface ITrainPlanService:IQueryService<TrainPlanViewResponse,TrainPlanDto,TrainPlanPagedRequest>,IApiService<TrainPlanDto,TrainPlan>
+    {
+    }
+}

+ 10 - 0
src/Hotline.Application/Exam/Interface/Train/ITrainRecordAnswerService.cs

@@ -0,0 +1,10 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Trains;
+using Hotline.Share.Dtos.Trains;
+
+namespace Exam.Application.Interface.Train
+{
+    public interface ITrainRecordAnswerService:IApiService<TrainRecordAnswerDto,TrainRecordAnswer>
+    {
+    }
+}

+ 19 - 0
src/Hotline.Application/Exam/Interface/Train/ITrainRecordService.cs

@@ -0,0 +1,19 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.Dtos.Trains;
+using Exam.Share.ViewResponses.Train;
+using Exam.Trains;
+using Hotline.Share.Dtos.Trains;
+using Hotline.Share.Requests.Train;
+
+namespace Exam.Application.Interface.Train
+{
+    public interface ITrainRecordService:IQueryService<TrainRecordViewResponse,TrainRecordDto,TrainRecordPagedRequest>,IApiService<TrainRecordDto,TrainRecord>
+    {
+        /// <summary>
+        /// 结束培训
+        /// </summary>
+        /// <param name="completeTrainRecordDto"></param>
+        /// <returns></returns>
+        Task CompleteTrainRecordAsync(CompleteTrainRecordDto completeTrainRecordDto);
+    }
+}

+ 12 - 0
src/Hotline.Application/Exam/Interface/Train/ITrainTemplateService.cs

@@ -0,0 +1,12 @@
+using Exam.Insfrastructure.Service.Interface;
+using Exam.Share.ViewResponses.Train;
+using Exam.Trains;
+using Hotline.Share.Dtos.Trains;
+using Hotline.Share.Requests.Train;
+
+namespace Exam.Application.Interface.Train
+{
+    public interface ITrainTemplateService:IQueryService<TrainTemplateViewResponse,TrainTemplateDto,TrainTemplatePagedRequest>,IApiService<TrainTemplateDto,TrainTemplate>
+    {
+    }
+}

+ 11 - 0
src/Hotline.Application/Exam/Mappers/MapperConfigs.cs

@@ -0,0 +1,11 @@
+using Mapster;
+
+namespace Exam.Application.Mappers
+{
+    internal class MapperConfigs : IRegister
+    {
+        public void Register(TypeAdapterConfig config)
+        {
+        }
+    }
+}

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionAnswerQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Hotline.Application.Exam.QueryExtensions.Questions
+{
+    public static class QuestionAnswerQueryExtesions
+    {
+        public static Expression<Func<QuestionAnswer,bool>> GetExpression(this QuestionAnswerPagedRequest questionAnswerPagedRequest)
+        {
+            Expression<Func<QuestionAnswer, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionKnowladgeQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Hotline.Application.Exam.QueryExtensions.Questions
+{
+    public static class QuestionKnowladgeQueryExtesions
+    {
+        public static Expression<Func<QuestionKnowladge,bool>> GetExpression(this QuestionKnowladgePagedRequest questionKnowladgePagedRequest)
+        {
+            Expression<Func<QuestionKnowladge, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionOptionsQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Hotline.Application.Exam.QueryExtensions.Questions
+{
+    public static class QuestionOptionsQueryExtesions
+    {
+        public static Expression<Func<QuestionOptions,bool>> GetExpression(this QuestionOptionsPagedRequest questionOptionsPagedRequest)
+        {
+            Expression<Func<QuestionOptions, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 39 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionQueryExtesions.cs

@@ -0,0 +1,39 @@
+using Exam.ExamManages;
+using Exam.Infrastructure.Extensions;
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Hotline.Application.Exam.QueryExtensions.Questions
+{
+    public static class QuestionQueryExtesions
+    {
+        public static Expression<Func<Question,bool>> GetExpression(this QuestionPagedRequest questionPagedRequest)
+        {
+            Expression<Func<Question, bool>> expression = m => m.Id != null;
+
+            expression = expression.And(x => questionPagedRequest.DifficultyLevel == x.DifficultyLevel, questionPagedRequest.DifficultyLevel);
+            expression = expression.And(x => questionPagedRequest.Title == x.Title, questionPagedRequest.Title);
+
+            return expression;
+        }
+
+        public static Expression<Func<QuestionTag,bool>> GetQuestionTagExpression(this QuestionPagedRequest questionPagedRequest)
+        {
+            Expression<Func<QuestionTag, bool>> expression = m => m.Id != null;
+
+            expression = expression.And(x => questionPagedRequest.TagId == x.TagId, questionPagedRequest.TagId);
+
+            return expression;
+        }
+
+        public static Expression<Func<ExamTag, bool>> GetExamTagExpression(this QuestionPagedRequest questionPagedRequest)
+        {
+            Expression<Func<ExamTag, bool>> expression = m => m.Id != null;
+
+            expression = expression.And(x => questionPagedRequest.TagId == x.Id, questionPagedRequest.TagId);
+
+            return expression;
+        }
+    }
+}

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionSourcewareQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Exam.Application
+{
+    public static class QuestionSourcewareQueryExtesions
+    {
+        public static Expression<Func<QuestionSourceware,bool>> GetExpression(this QuestionSourcewarePagedRequest questionSourcewarePagedRequest)
+        {
+            Expression<Func<QuestionSourceware, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionTagQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Questions;
+using Hotline.Share.Requests.Question;
+using System.Linq.Expressions;
+
+namespace Exam.Application
+{
+    public static class QuestionTagQueryExtesions
+    {
+        public static Expression<Func<QuestionTag,bool>> GetExpression(this QuestionTagPagedRequest questionTagPagedRequest)
+        {
+            Expression<Func<QuestionTag, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Sourcewares/SourcewareCategoryQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using System.Linq.Expressions;
+
+namespace Exam.Application.QueryExtensions.Sourcewares
+{
+    public static class SourcewareCategoryQueryExtesions
+    {
+        public static Expression<Func<SourcewareCategory,bool>> GetExpression(this SourcewareCategoryRequest sourcewareCategoryPagedRequest)
+        {
+            Expression<Func<SourcewareCategory, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 20 - 0
src/Hotline.Application/Exam/QueryExtensions/Sourcewares/SourcewareQueryExtensions.cs

@@ -0,0 +1,20 @@
+using Exam.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using System.Linq.Expressions;
+
+namespace Exam.Application.QueryExtensions.Sourcewares
+{
+    public static class SourcewareQueryExtesions
+    {
+        public static Expression<Func<Sourceware,bool>> GetExpression(this SourcewarePagedRequest sourcewarePagedRequest)
+        {
+            Expression<Func<Sourceware, bool>> expression = m => m.Id != null;
+
+            return expression;
+        }
+
+    }
+}
+
+
+

+ 77 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionAnswerService.cs

@@ -0,0 +1,77 @@
+using Exam.Application;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Application.Exam.QueryExtensions.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.Questions
+{
+    /// <summary>
+    /// 试题参考答案服务
+    /// </summary>
+    [Description("试题参考答案服务")]
+    public class QuestionAnswerService : ApiService<QuestionAnswer, QuestionAnswerDto,HotlineDbContext>,IQuestionAnswerService, IScopeDependency
+    {
+        private readonly IQuestionAnswerRepository _repository;
+        public QuestionAnswerService(IQuestionAnswerRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<QuestionAnswerDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<QuestionAnswerDto>();
+        }
+
+        public async Task<(int, List<QuestionAnswerViewResponse>)> GetListAsync(QuestionAnswerPagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new QuestionAnswerViewResponse { 
+                				QuestionId = m.QuestionId,
+								Answer = m.Answer,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<QuestionAnswerViewResponse>> GetPagedListAsync(QuestionAnswerPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionAnswerTable = _repository.Queryable().Where(expression);
+
+            var queryable = questionAnswerTable.Select((m) => new QuestionAnswerViewResponse
+            {
+                				QuestionId = m.QuestionId,
+								Answer = m.Answer,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionAnswerViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 76 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionKnowladgeService.cs

@@ -0,0 +1,76 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Application.Exam.QueryExtensions.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 关联知识服务
+    /// </summary>
+    [Description("关联知识服务")]
+    public class QuestionKnowladgeService : ApiService<QuestionKnowladge, QuestionKnowladgeDto,HotlineDbContext>,IQuestionKnowladgeService, IScopeDependency
+    {
+        private readonly IQuestionKnowladgeRepository _repository;
+        public QuestionKnowladgeService(IQuestionKnowladgeRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<QuestionKnowladgeDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<QuestionKnowladgeDto>();
+        }
+
+        public async Task<(int, List<QuestionKnowladgeViewResponse>)> GetListAsync(QuestionKnowladgePagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new QuestionKnowladgeViewResponse { 
+                				QuestionId = m.QuestionId,
+								KnowladgeId = m.KnowladgeId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<QuestionKnowladgeViewResponse>> GetPagedListAsync(QuestionKnowladgePagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionKnowladgeTable = _repository.Queryable().Where(expression);
+
+            var queryable = questionKnowladgeTable.Select((m) => new QuestionKnowladgeViewResponse
+            {
+                				QuestionId = m.QuestionId,
+								KnowladgeId = m.KnowladgeId,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionKnowladgeViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 79 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionOptionsService.cs

@@ -0,0 +1,79 @@
+using Exam.Application;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Application.Exam.QueryExtensions.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.Questions
+{
+    /// <summary>
+    /// 试题选项服务
+    /// </summary>
+    [Description("试题选项服务")]
+    public class QuestionOptionsService : ApiService<QuestionOptions, QuestionOptionsDto,HotlineDbContext>,IQuestionOptionsService, IScopeDependency
+    {
+        private readonly IQuestionOptionsRepository _repository;
+        public QuestionOptionsService(IQuestionOptionsRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<QuestionOptionsDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<QuestionOptionsDto>();
+        }
+
+        public async Task<(int, List<QuestionOptionsViewResponse>)> GetListAsync(QuestionOptionsPagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new QuestionOptionsViewResponse { 
+                				Content = m.Content,
+								IsAnswer = m.IsAnswer,
+								QuestionId = m.QuestionId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<QuestionOptionsViewResponse>> GetPagedListAsync(QuestionOptionsPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionOptionsTable = _repository.Queryable().Where(expression);
+
+            var queryable = questionOptionsTable.Select((m) => new QuestionOptionsViewResponse
+            {
+                				Content = m.Content,
+								IsAnswer = m.IsAnswer,
+								QuestionId = m.QuestionId,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionOptionsViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 145 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionService.cs

@@ -0,0 +1,145 @@
+using Exam.Application;
+using Exam.Application.Interface.Questions;
+using Exam.ExamManages;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Repository.Sqlsugar.Repositories;
+using Exam.Share.ViewResponses.Question;
+using Hotline.Application.Exam.QueryExtensions.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.Questions
+{
+    public class QuestionService : ApiService<Question, QuestionDto,HotlineDbContext>, IQuestionService, IScopeDependency
+    {
+        #region ctor
+        private readonly IQuestionRepository _repository;
+        private readonly IQuestionTagRepository _questionTagRepository;
+        private readonly IQuestionOptionsRepository _questionOptionRepository;
+        private readonly IDataPermissionFilterBuilder _dataPermissionFilterBuilder;
+        private readonly IServiceProvider _serviceProvider;
+        public QuestionService(IQuestionRepository repository,
+            IQuestionTagRepository questionTagRepository,
+            IQuestionOptionsRepository questionOptionsRepository,
+            IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider
+            ) : base(repository)
+        {
+            _repository = repository;
+            _questionTagRepository = questionTagRepository;
+            _questionOptionRepository = questionOptionsRepository;
+            _dataPermissionFilterBuilder = dataPermissionFilterBuilder;
+            _serviceProvider = serviceProvider;
+        }
+        #endregion
+
+        #region public method
+        public async Task<QuestionDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            var questionDto = entity.Adapt<QuestionDto>();
+
+            return questionDto;
+        }
+
+        public async Task<(int, List<QuestionViewResponse>)> GetListAsync(QuestionPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionTable = _repository.Queryable().Where(expression);
+
+            var questionTagExpression = queryRequest.GetQuestionTagExpression();
+            var questionTagTable = new ExamRepository<QuestionTag>(_repository.UOW,_dataPermissionFilterBuilder,_serviceProvider).Queryable().Where(questionTagExpression);
+
+            var examTagExpression = queryRequest.GetExamTagExpression();
+            var examTagTable = new ExamRepository<ExamTag>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(examTagExpression);
+
+
+            var queryable = questionTable.InnerJoin(questionTagTable, (s, d) => s.Id == d.QuestionId).InnerJoin(examTagTable, (f, s, t) => s.TagId == t.Id).Select((s, d, f) => new QuestionViewResponse
+            {
+                DifficultyLevel = s.DifficultyLevel,
+                FormalEnable = s.FormalEnable,
+                SimulateEnable = s.SimulateEnable,
+                SortIndex = s.SortIndex,
+                Status = s.Status,
+                Tag = f.Name
+            });
+
+            var result = await queryable.ToListAsync();
+
+            var total = await queryable.CountAsync();
+
+            return (total, result);
+        }
+
+        public async Task<PageViewResponse<QuestionViewResponse>> GetPagedListAsync(QuestionPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionTable = _repository.Queryable().Where(expression);
+
+            var questionTagExpression = queryRequest.GetQuestionTagExpression();
+            var questionTagTable = new BaseRepository<QuestionTag>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(questionTagExpression);
+
+            var examTagExpression = queryRequest.GetExamTagExpression();
+            var examTagTable = new BaseRepository<ExamTag>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(examTagExpression);
+
+
+            var queryable = questionTable.InnerJoin(questionTagTable, (s, d) => s.Id == d.QuestionId).InnerJoin(examTagTable, (f, s, t) => s.TagId == t.Id).Select((s, d, f) => new QuestionViewResponse
+            {
+                DifficultyLevel = s.DifficultyLevel,
+                FormalEnable = s.FormalEnable,
+                SimulateEnable = s.SimulateEnable,
+                SortIndex = s.SortIndex,
+                Status = s.Status,
+                Tag = f.Name
+            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        } 
+
+        public override async Task AddAsync(QuestionDto actionRequest, CancellationToken cancellationToken)
+        {
+            await base.AddAsync(actionRequest, cancellationToken);
+
+            await AddQuestionTags(actionRequest, cancellationToken);
+
+            await AddQuestionOptions(actionRequest, cancellationToken);
+        }
+        #endregion
+
+        #region private method
+        private async Task AddQuestionTags(QuestionDto actionRequest, CancellationToken cancellationToken)
+        {
+            if (actionRequest.QuestionTagDtos == null) return;
+
+            var questionTags = actionRequest.QuestionTagDtos.Adapt<QuestionTag>();
+
+            await _questionTagRepository.AddWithValidateAsync(questionTags,cancellationToken);           
+        }
+
+        private async Task AddQuestionOptions(QuestionDto actionRequest, CancellationToken cancellationToken)
+        {
+            if (actionRequest.QuestionOptionsDtos == null) return;
+
+            var questionOptionses = actionRequest.QuestionOptionsDtos.Adapt<QuestionOptions>();
+
+            await _questionOptionRepository.AddWithValidateAsync(questionOptionses, cancellationToken);
+        }
+        #endregion
+    }
+}

+ 76 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionSourcewareService.cs

@@ -0,0 +1,76 @@
+using Exam.Application;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.Questions
+{
+    /// <summary>
+    /// 关联课件服务
+    /// </summary>
+    [Description("关联课件服务")]
+    public class QuestionSourcewareService : ApiService<QuestionSourceware, QuestionSourcewareDto,HotlineDbContext>,IQuestionSourcewareService, IScopeDependency
+    {
+        private readonly IQuestionSourcewareRepository _repository;
+        public QuestionSourcewareService(IQuestionSourcewareRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<QuestionSourcewareDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<QuestionSourcewareDto>();
+        }
+
+        public async Task<(int, List<QuestionSourcewareViewResponse>)> GetListAsync(QuestionSourcewarePagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new QuestionSourcewareViewResponse { 
+                				QuestionId = m.QuestionId,
+								SourcewareId = m.SourcewareId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<QuestionSourcewareViewResponse>> GetPagedListAsync(QuestionSourcewarePagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionSourcewareTable = _repository.Queryable().Where(expression);
+
+            var queryable = questionSourcewareTable.Select((m) => new QuestionSourcewareViewResponse
+            {
+                				QuestionId = m.QuestionId,
+								SourcewareId = m.SourcewareId,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionSourcewareViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 75 - 0
src/Hotline.Application/Exam/Service/Questions/QuestionTagService.cs

@@ -0,0 +1,75 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Requests.Question;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Exam.Application
+{
+    /// <summary>
+    /// 试题标签服务
+    /// </summary>
+    [Description("试题标签服务")]
+    public class QuestionTagService : ApiService<QuestionTag, QuestionTagDto,HotlineDbContext>,IQuestionTagService, IScopeDependency
+    {
+        private readonly IQuestionTagRepository _repository;
+        public QuestionTagService(IQuestionTagRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<QuestionTagDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<QuestionTagDto>();
+        }
+
+        public async Task<(int, List<QuestionTagViewResponse>)> GetListAsync(QuestionTagPagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new QuestionTagViewResponse { 
+                				TagId = m.TagId,
+								QuestionId = m.QuestionId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<QuestionTagViewResponse>> GetPagedListAsync(QuestionTagPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var questionTagTable = _repository.Queryable().Where(expression);
+
+            var queryable = questionTagTable.Select((m) => new QuestionTagViewResponse
+            {
+                				TagId = m.TagId,
+								QuestionId = m.QuestionId,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<QuestionTagViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 56 - 0
src/Hotline.Application/Exam/Service/Sourcewares/SourcewareCategoryService.cs

@@ -0,0 +1,56 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Share.Dtos.Sourcewares;
+using Exam.Share.ViewResponses.Sourceware;
+using Exam.Sourcewares;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Exam.Application.Service.Sourcewares
+{
+    /// <summary>
+    /// 课件类型服务
+    /// </summary>
+    [Description("课件类型服务")]
+    public class SourcewareCategoryService : ApiService<SourcewareCategory, SourcewareCategoryDto,HotlineDbContext>,ISourcewareCategoryService, IScopeDependency
+    {
+        private readonly ISourcewareCategoryRepository _repository;
+        public SourcewareCategoryService(ISourcewareCategoryRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<SourcewareCategoryDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<SourcewareCategoryDto>();
+        }
+
+        public async Task<(int, List<SourcewareCategoryViewResponse>)> GetListAsync(SourcewareCategoryRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new SourcewareCategoryViewResponse { 
+                				Name = m.Name,
+								ParentId = m.ParentId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<SourcewareCategoryViewResponse>> GetPagedListAsync(SourcewareCategoryRequest queryRequest)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
+
+

+ 80 - 0
src/Hotline.Application/Exam/Service/Sourcewares/SourcewareService.cs

@@ -0,0 +1,80 @@
+using Exam.Application;
+using Exam.Application.Interface.Sourcewares;
+using Exam.Application.QueryExtensions.Sourcewares;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Share;
+using Exam.Share.Dtos.Sourcewares;
+using Exam.Sourcewares;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using Mapster;
+using System.ComponentModel;
+using XF.Domain.Dependency;
+
+namespace Exam.Application.Service.Sourcewares
+{
+    /// <summary>
+    /// 课件服务
+    /// </summary>
+    [Description("课件服务")]
+    public class SourcewareService : ApiService<Sourceware, SourcewareDto,HotlineDbContext>,ISourcewareService, IScopeDependency
+    {
+        private readonly ISourcewareRepository _repository;
+        public SourcewareService(ISourcewareRepository repository) : base(repository)
+        {
+            _repository = repository;
+        }
+
+        public async Task<SourcewareDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            var entity = await _repository.GetAsync(entityQueryRequest.Id);
+
+            return entity.Adapt<SourcewareDto>();
+        }
+
+        public async Task<(int, List<SourcewareViewResponse>)> GetListAsync(SourcewarePagedRequest queryRequest)
+        {
+            var query = _repository.Queryable();
+
+            var result = await query.Select(m => new SourcewareViewResponse { 
+                				Name = m.Name,
+								CategoryId = m.CategoryId,
+								AttachmentId = m.AttachmentId,
+								Id = m.Id,
+				            }).ToListAsync();
+
+            var total = await query.CountAsync();
+
+            return (total,result);
+        }
+
+        public async Task<PageViewResponse<SourcewareViewResponse>> GetPagedListAsync(SourcewarePagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var sourcewareTable = _repository.Queryable().Where(expression);
+
+            var queryable = sourcewareTable.Select((m) => new SourcewareViewResponse
+            {
+                				Name = m.Name,
+								CategoryId = m.CategoryId,
+								AttachmentId = m.AttachmentId,
+								Id = m.Id,
+				            });
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new PageViewResponse<SourcewareViewResponse>
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+    }
+}
+
+

+ 8 - 0
src/Hotline.Application/Hotline.Application.csproj

@@ -16,6 +16,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\01.Infrastructure\Exam.Insfrastructure.Service\Exam.Insfrastructure.Service.csproj" />
     <ProjectReference Include="..\Hotline.Ai.Jths\Hotline.Ai.Jths.csproj" />
     <ProjectReference Include="..\Hotline.Ai.XingTang\Hotline.Ai.XingTang.csproj" />
     <ProjectReference Include="..\Hotline.Application.Contracts\Hotline.Application.Contracts.csproj" />
@@ -30,4 +31,11 @@
     <ProjectReference Include="..\XingTang.Sdk\XingTang.Sdk.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Exam\Service\ExamManages\" />
+    <Folder Include="Exam\Service\Practices\" />
+    <Folder Include="Exam\Service\TestPapers\" />
+    <Folder Include="Exam\Service\Trains\" />
+  </ItemGroup>
+
 </Project>

+ 113 - 0
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarExtensions.cs

@@ -0,0 +1,113 @@
+using System.Collections;
+using System.Data;
+using System.Reflection;
+using SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Extensions
+{
+    public static class SqlSugarExtensions
+    {
+        /// <summary>
+        /// List转Dictionary
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="list"></param>
+        /// <returns></returns>
+        public static List<Dictionary<string, object?>> ToDictionary<T>(this List<T> list)
+        {
+            var result = new List<Dictionary<string, object?>>();
+            if (list.Any())
+            {
+                foreach (var item in list)
+                {
+                    Dictionary<string, object?> dc = new();
+                    var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        dc.Add(property.Name, property.GetValue(item));
+                    }
+                    result.Add(dc);
+                }
+            }
+
+            return result;
+        }
+
+        public static DataTable ToDataTable<T>(this List<T> list, string tableName)
+        {
+            var dt = new DataTable();
+            dt.TableName = tableName; //设置表名
+
+            if (list.Any())
+            {
+                PropertyInfo[] properties = list[0].GetType().GetProperties();
+                foreach (PropertyInfo property in properties)
+                {
+                    if (IsIgnoreColumn(property)) continue;
+                    if (IsNavigateColumn(property)) continue;
+                    Type colType = property.PropertyType;
+                    if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                    {
+                        colType = colType.GetGenericArguments()[0];
+                    }
+                    dt.Columns.Add(property.Name, colType);
+                }
+
+                foreach (var item in list)
+                {
+                    ArrayList tempList = new();
+                    //var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        var obj = property.GetValue(item, null);
+                        tempList.Add(obj);
+                    }
+                    dt.LoadDataRow(tempList.ToArray(), true);
+                }
+
+                //for (int i = 0; i < list.Count; i++)
+                //{
+                //    ArrayList tempList = new();
+                //    foreach (PropertyInfo pi in propertys)
+                //    {
+                //        if (IsIgnoreColumn(pi))
+                //            continue;
+                //        object obj = pi.GetValue(list[i], null);
+                //        tempList.Add(obj);
+                //    }
+                //    object[] array = tempList.ToArray();
+                //    result.LoadDataRow(array, true);
+                //}
+            }
+
+            //var addRow = dt.NewRow();
+            //addRow["id"] = 0;
+            //addRow["price"] = 1;
+            //addRow["Name"] = "a";
+            //dt.Rows.Add(addRow);//添加数据
+
+            //var x = db.Storageable(dt).WhereColumns("id").ToStorage();//id作为主键
+            //x.AsInsertable.IgnoreColumns("id").ExecuteCommand();//如果是自增要添加IgnoreColumns
+            //x.AsUpdateable.ExecuteCommand();
+            return dt;
+        }
+
+        /// <summary>
+        /// 排除SqlSugar忽略的列
+        /// </summary>
+        /// <param name="pi"></param>
+        /// <returns></returns>
+        private static bool IsIgnoreColumn(PropertyInfo property)
+        {
+            var sc = property.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore);
+            return sc != null;
+        }
+
+        private static bool IsNavigateColumn(PropertyInfo property) =>
+            property.GetCustomAttributes<Navigate>(false).Any();
+    }
+}

+ 22 - 0
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarRepositoryExtensions.cs

@@ -0,0 +1,22 @@
+using SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Extensions
+{
+    public static class SqlSugarRepositoryExtensions
+    {
+        public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
+            where TEntity : class, new()
+        {
+            RefAsync<int> total = 0;
+            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+        public static Task<List<TEntity>> ToFixedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int queryIndex, int? queryCount = null, CancellationToken cancellationToken = default)
+          where TEntity : class, new()
+        {
+            if (queryCount is null or 0) queryCount = 50;
+            return query.Skip(queryIndex * queryCount.Value).Take(queryCount.Value).ToListAsync(cancellationToken);
+        }
+    }
+}

+ 285 - 0
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarStartupExtensions.cs

@@ -0,0 +1,285 @@
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq.Dynamic.Core;
+using System.Linq.Expressions;
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Extensions;
+using XF.Domain.Options;
+using XF.Domain.Repository;
+using XF.Utility.SequentialId;
+
+namespace Hotline.Repository.SqlSugar.Exam.Extensions
+{
+    public static class SqlSugarStartupExtensions
+    {
+        public static void AddSqlSugar(this IServiceCollection services, IConfiguration configuration, string dbName = "Exam")
+        {
+            //多租户 new SqlSugarScope(List<ConnectionConfig>,db=>{});
+
+            SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
+            {
+                DbType = DbType.PostgreSQL,
+                ConnectionString = configuration.GetConnectionString(dbName),
+                IsAutoCloseConnection = true,
+                ConfigureExternalServices = new ConfigureExternalServices
+                {
+                    EntityService = (property, column) =>
+                    {
+                        var attributes = property.GetCustomAttributes(true); //get all attributes 
+
+                        if (column.PropertyName.ToLower() == "id" ||
+                            attributes.Any(it => it is KeyAttribute)) //是id的设为主键
+                        {
+                            column.IsPrimarykey = true;
+                            column.Length = 36;
+                        }
+
+                        //if (!column.DbColumnName.Contains("_"))
+                        //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
+
+                        //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
+
+                        //统一设置 nullable等于isnullable=true
+                        if (!column.IsPrimarykey && new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
+                        {
+                            column.IsNullable = true;
+                        }
+                    },
+                    EntityNameService = (type, entity) =>
+                    {
+                        var attributes = type.GetCustomAttributes(true);
+                        //if (attributes.Any(it => it is TableAttribute))
+                        //{
+                        //    entity.DbTableName = (attributes.First(it => it is TableAttribute) as TableAttribute).UserName;
+                        //}
+
+                        entity.DbTableName = entity.DbTableName.ToSnakeCase();
+
+                        //if (!entity.DbTableName.Contains("_"))
+                        //    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);//ToUnderLine驼峰转下划线方法
+                        if (attributes.Any(d => d is DescriptionAttribute))
+                        {
+                            entity.TableDescription =
+                                (attributes.First(d => d is DescriptionAttribute) as DescriptionAttribute)
+                                .Description;
+                        }
+                    }
+                },
+                MoreSettings = new ConnMoreSettings
+                {
+                    PgSqlIsAutoToLower = false,//增删查改支持驼峰表
+                    PgSqlIsAutoToLowerCodeFirst = false, // 建表建驼峰表。5.1.3.30 
+                }
+            },
+                SetDbAop
+            );
+
+            ISugarUnitOfWork<HotlineDbContext> context = new SugarUnitOfWork<HotlineDbContext>(sqlSugar);
+            services.AddSingleton(context);
+
+            InitDatabase(context, configuration);
+        }
+
+        private static void InitDatabase(ISugarUnitOfWork<HotlineDbContext> context, IConfiguration configuration)
+        {
+            var dbOptions = configuration.GetSection("DatabaseConfiguration").Get<DatabaseOptions>() ?? new DatabaseOptions();
+            if (dbOptions.ApplyDbMigrations)
+            {
+                context.Db.DbMaintenance.CreateDatabase();
+                
+                var types = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes())
+                    .Where(d => !d.IsInterface
+                                && !d.IsAbstract
+                                && d.IsClass
+                                && d.GetInterfaces().Any(x => x == typeof(ITable)))
+                    .ToArray();
+
+                context.Db.CodeFirst.InitTables(types);//根据types创建表
+            }
+
+            if (dbOptions.ApplySeed)
+            {
+                var allTypes = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes());
+
+                var seedDataTypes = allTypes.Where(d => !d.IsInterface && !d.IsAbstract && d.IsClass
+                                                        && d.HasImplementedOf(typeof(ISeedData<>)));
+
+                foreach (var seedType in seedDataTypes)
+                {
+                    var instance = Activator.CreateInstance(seedType);
+
+                    var hasDataMethod = seedType.GetMethod("HasData");
+                    var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
+                    if (seedData == null) continue;
+
+                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
+
+                    var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
+                    if (entityInfo.Columns.Any(d => d.IsPrimarykey))
+                    {
+                        var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
+                        storage.AsInsertable.ExecuteCommand();
+                    }
+                    else
+                    {
+                        // 无主键则只进行插入
+                        if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
+                            context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
+                    }
+                }
+
+            }
+        }
+
+        #region private
+
+        private static void SetDbAop(SqlSugarClient db)
+        {
+            /***写AOP等方法***/
+            db.Aop.OnLogExecuting = (sql, pars) =>
+            {
+                //Log.Information("Sql: {0}", sql);
+                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
+            };
+            db.Aop.OnError = (exp) =>//SQL报错
+            {
+                //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
+                Log.Error("SqlError: {0}", exp.Sql);
+
+                //5.0.8.2 获取无参数化 SQL  对性能有影响,特别大的SQL参数多的,调试使用
+                //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
+            };
+            //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
+            //{
+            //    //sql=newsql
+            //    //foreach (var p in pars) //修改
+            //    //{
+
+            //    //}
+
+            //    return new KeyValuePair<string, SugarParameter[]>(sql, pars);
+            //};
+
+            db.Aop.OnLogExecuted = (sql, p) =>
+            {
+                //执行时间超过1秒
+                if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
+                {
+                    //代码CS文件名
+                    var fileName = db.Ado.SqlStackTrace.FirstFileName;
+                    //代码行数
+                    var fileLine = db.Ado.SqlStackTrace.FirstLine;
+                    //方法名
+                    var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
+                    //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息
+
+                    Log.Warning("slow query ==> fileName: {fileName}, fileLine: {fileLine}, FirstMethodName: {FirstMethodName}",
+                        fileName, fileLine, FirstMethodName);
+                    Log.Warning(UtilMethods.GetNativeSql(sql, p));
+                    Log.Warning("slow query totalSeconds: {sec}", db.Ado.SqlExecutionTime.TotalSeconds);
+                }
+                //相当于EF的 PrintToMiniProfiler
+            };
+
+            db.Aop.DataExecuting = (oldValue, entityInfo) =>
+            {
+                //inset生效
+                if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
+                {
+                    if (oldValue is DateTime createTime)
+                    {
+                        if (createTime == DateTime.MinValue)
+                        {
+                            entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
+                                                              //entityInfo有字段所有参数
+                        }
+                    }
+                }
+                //update生效        
+                else if (entityInfo.PropertyName == "LastModificationTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
+                {
+                    entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
+                }
+
+                //根据当前列修改另一列 可以么写
+                //if(当前列逻辑==XXX)
+                //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
+                //if(properyDate!=null)
+                //properyDate.SetValue(entityInfo.EntityValue,1);
+
+                else if (entityInfo.EntityColumnInfo.IsPrimarykey
+                         && entityInfo.EntityColumnInfo.PropertyName.ToLower() == "id"
+                         && entityInfo.OperationType == DataFilterType.InsertByObject
+                         && oldValue is null) //通过主键保证只进一次事件
+                {
+                    //var propertyId = entityInfo.EntityValue.GetType().GetProperty("Id");
+                    //if (propertyId is not null)
+                    //{
+                    //    var idValue = propertyId.GetValue(entityInfo.EntityValue);
+                    //    if (idValue is null)
+                    //        //这样每条记录就只执行一次 
+                    //        entityInfo.SetValue(SequentialStringGenerator.Create());
+                    //}
+                    entityInfo.SetValue(SequentialStringGenerator.Create(EStringFormat.Ulid));
+
+                }
+            };
+
+            SetDeletedEntityFilter(db);
+        }
+
+        private static void SetDeletedEntityFilter(SqlSugarClient db)
+        {
+            var cacheKey = $"DbFilter:{db.CurrentConnectionConfig.ConfigId}:IsDeleted";
+            var tableFilterItemList = db.DataCache.Get<List<TableFilterItem<object>>>(cacheKey);
+            if (tableFilterItemList == null)
+            {
+                // 获取基类实体数据表
+                var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes())
+                    .Where(d => !d.IsInterface
+                                && !d.IsAbstract
+                                && d.IsClass
+                                && d.GetInterfaces().Any(x => x == typeof(ISoftDelete)));
+                if (!entityTypes.Any()) return;
+
+                var tableFilterItems = new List<TableFilterItem<object>>();
+                foreach (var entityType in entityTypes)
+                {
+                    if (entityType.GetProperty("IsDeleted") is null) continue;
+                    //// 排除非当前数据库实体
+                    //var tAtt = entityType.GetCustomAttribute<TenantAttribute>();
+                    //if ((tAtt != null && (string)db.CurrentConnectionConfig.ConfigId != tAtt.configId.ToString()) ||
+                    //    (tAtt == null && (string)db.CurrentConnectionConfig.ConfigId != SqlSugarConst.ConfigId))
+                    //    continue;
+
+                    var lambda = DynamicExpressionParser.ParseLambda(new[] {
+                        Expression.Parameter(entityType, "d") },
+                    typeof(bool),
+                    $"{nameof(SoftDeleteEntity.IsDeleted)} == @0", false);
+                    var tableFilterItem = new TableFilterItem<object>(entityType, lambda);
+                    tableFilterItems.Add(tableFilterItem);
+                    db.QueryFilter.Add(tableFilterItem);
+                }
+                db.DataCache.Add(cacheKey, tableFilterItems);
+            }
+            else
+            {
+                tableFilterItemList.ForEach(u =>
+                {
+                    db.QueryFilter.Add(u);
+                });
+            }
+        }
+
+        #endregion
+    }
+}

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamAnswerRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 考试答案仓储接口
+    /// </summary>
+    [Description("考试答案仓储接口")]
+    public interface IExamAnswerRepository:IRepository<ExamAnswer>,IExamRepository<ExamAnswer,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamManageRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 考试管理仓储接口
+    /// </summary>
+    [Description("考试管理仓储接口")]
+    public interface IExamManageRepository:IRepository<ExamManage>,IExamRepository<ExamManage,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamQuestionScoreRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 题型分数仓储接口
+    /// </summary>
+    [Description("题型分数仓储接口")]
+    public interface IExamQuestionScoreRepository:IRepository<ExamQuestionScore>,IExamRepository<ExamQuestionScore,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExamTagRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 考试标签仓储接口
+    /// </summary>
+    [Description("考试标签仓储接口")]
+    public interface IExamTagRepository:IRepository<ExamTag>,IExamRepository<ExamTag,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IExtractRuleRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 抽题规则仓储接口
+    /// </summary>
+    [Description("抽题规则仓储接口")]
+    public interface IExtractRuleRepository:IRepository<ExtractRule>,IExamRepository<ExtractRule,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IRuleTagRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 规则标签仓储接口
+    /// </summary>
+    [Description("规则标签仓储接口")]
+    public interface IRuleTagRepository:IRepository<RuleTag>,IExamRepository<RuleTag,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamItemRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 用户考试明细仓储接口
+    /// </summary>
+    [Description("用户考试明细仓储接口")]
+    public interface IUserExamItemRepository:IRepository<UserExamItem>,IExamRepository<UserExamItem,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 21 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamRepository.cs

@@ -0,0 +1,21 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.ExamManages;
+using Hotline.Repository.SqlSugar;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 用户考试仓储接口
+    /// </summary>
+    [Description("用户考试仓储接口")]
+    public interface IUserExamRepository:IRepository<UserExam>,IExamRepository<UserExam,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 20 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeAnswerRepository.cs

@@ -0,0 +1,20 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.Practices;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.Practices
+{
+    /// <summary>
+    /// 练习答案仓储接口
+    /// </summary>
+    [Description("练习答案仓储接口")]
+    public interface IPracticeAnswerRepository:IRepository<PracticeAnswer>,IExamRepository<PracticeAnswer,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 20 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeQuestionRepository.cs

@@ -0,0 +1,20 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.Practices;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.Practices
+{
+    /// <summary>
+    /// 练习题目仓储接口
+    /// </summary>
+    [Description("练习题目仓储接口")]
+    public interface IPracticeQuestionRepository:IRepository<PracticeQuestion>,IExamRepository<PracticeQuestion,HotlineDbContext>
+    {
+       
+    }
+}
+
+

+ 20 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/Practices/IPracticeRecordRepository.cs

@@ -0,0 +1,20 @@
+
+using SqlSugar;
+using XF.Domain.Repository;
+using Exam.Insfrastructure.Service.Interface;
+using System.ComponentModel;
+using Exam.Practices;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.Practices
+{
+    /// <summary>
+    /// 练习记录仓储接口
+    /// </summary>
+    [Description("练习记录仓储接口")]
+    public interface IPracticeRecordRepository:IRepository<PracticeRecord>,IExamRepository<PracticeRecord,HotlineDbContext>
+    {
+       
+    }
+}
+
+

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно