瀏覽代碼

合并考试系统代码

guqiang 2 月之前
父節點
當前提交
5f3a384d23
共有 100 個文件被更改,包括 5280 次插入617 次删除
  1. 19 0
      Hotline.Repository.Sqlsugar/Interfaces/IExamTagRepository.cs
  2. 33 0
      Hotline.sln
  3. 23 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Attributes/EntityMapperAttribute.cs
  4. 7 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Class1.cs
  5. 85 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/ActionRequest.cs
  6. 19 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/EntityQueryRequest.cs
  7. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/PageViewResponse.cs
  8. 103 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Entity/Pagination.cs
  9. 17 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Exam.Infrastructure.Data.csproj
  10. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IActionRequest.cs
  11. 6 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IPagedViewResponse.cs
  12. 6 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IQueryRequest.cs
  13. 13 0
      src/01.Infrastructure/Exam.Infrastructure.Data/Interface/IViewResponse.cs
  14. 7 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Class1.cs
  15. 8 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Constants/InfrastructureContant.cs
  16. 16 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Enums/ValidationErrorType.cs
  17. 17 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Exam.Infrastructure.Validation.csproj
  18. 73 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Exceptions/DomainException.cs
  19. 67 0
      src/01.Infrastructure/Exam.Infrastructure.Validation/Extensions/ValidateResultExtensions.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. 15 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Constants/RouteApi.cs
  27. 14 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Exam.Infrastructure.Web.csproj
  28. 123 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/AssemblyUtility.cs
  29. 64 0
      src/01.Infrastructure/Exam.Infrastructure.Web/Utilities/ServiceUtility.cs
  30. 10 0
      src/01.Infrastructure/Exam.Infrastructure/Attributes/ReflectIgnoreAttribute.cs
  31. 7 0
      src/01.Infrastructure/Exam.Infrastructure/Class1.cs
  32. 32 0
      src/01.Infrastructure/Exam.Infrastructure/Enums/OperationStatus.cs
  33. 15 0
      src/01.Infrastructure/Exam.Infrastructure/Exam.Infrastructure.csproj
  34. 47 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/EnumExtension.cs
  35. 114 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ExpressionExtensions.cs
  36. 514 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ObjectExtension.cs
  37. 55 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/ParameterRebinder.cs
  38. 530 0
      src/01.Infrastructure/Exam.Infrastructure/Extensions/StringExtension.cs
  39. 244 0
      src/01.Infrastructure/Exam.Infrastructure/Utilities/BaseAssemblyUtility.cs
  40. 7 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Class1.cs
  41. 9 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Entitys/StatusActionRequest.cs
  42. 20 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Exam.Insfrastructure.Service.csproj
  43. 79 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Extensions/ApiServiceExtension.cs
  44. 23 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IApiService.cs
  45. 6 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IDomainService.cs
  46. 55 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IExamRepository.cs
  47. 17 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Interface/IQueryService.cs
  48. 122 0
      src/01.Infrastructure/Exam.Insfrastructure.Service/Service/ApiService.cs
  49. 8 5
      src/Hotline.Api/Controllers/ArticleController.cs
  50. 194 24
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  51. 155 0
      src/Hotline.Api/Controllers/EnterpriseLuzhouController.cs
  52. 1 130
      src/Hotline.Api/Controllers/EnterprisesController.cs
  53. 23 0
      src/Hotline.Api/Controllers/Exam/QuestionController.cs
  54. 89 0
      src/Hotline.Api/Controllers/Exam/SourcewareCategoryController.cs
  55. 88 0
      src/Hotline.Api/Controllers/Exam/SourcewaresController.cs
  56. 777 0
      src/Hotline.Api/Controllers/FwThirdController.cs
  57. 3 1
      src/Hotline.Api/Controllers/HomeController.cs
  58. 1 1
      src/Hotline.Api/Controllers/IPPbxController.cs
  59. 316 386
      src/Hotline.Api/Controllers/OrderController.cs
  60. 6 30
      src/Hotline.Api/Controllers/TestController.cs
  61. 1 0
      src/Hotline.Api/Controllers/UserController.cs
  62. 3 3
      src/Hotline.Api/Controllers/WebPortalController.cs
  63. 1 1
      src/Hotline.Api/Controllers/WorkflowController.cs
  64. 二進制
      src/Hotline.Api/Documents/luzhou/丰窝12345平台工单接入接口(V1.1-20250225) .docx
  65. 11 8
      src/Hotline.Api/Realtimes/RealtimeMethods.cs
  66. 26 25
      src/Hotline.Api/Realtimes/RealtimeService.cs
  67. 1 1
      src/Hotline.Api/StartupExtensions.cs
  68. 1 0
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  69. 2 2
      src/Hotline.Application/Enterprise/EnterpriseApplication.cs
  70. 9 0
      src/Hotline.Application/Exam/Constants/SourcewareCategoryRouteApi.cs
  71. 9 0
      src/Hotline.Application/Exam/Constants/SourcewareRouteApi.cs
  72. 25 0
      src/Hotline.Application/Exam/Extensions/ActionRequestExtensions.cs
  73. 13 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExamManageService.cs
  74. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExamTagService.cs
  75. 13 0
      src/Hotline.Application/Exam/Interface/ExamManages/IExtractRuleService.cs
  76. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUnExamUserService.cs
  77. 12 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamResultService.cs
  78. 29 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs
  79. 13 0
      src/Hotline.Application/Exam/Interface/Practices/IPracticeService.cs
  80. 21 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionAnswerService.cs
  81. 21 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionKnowladgeService.cs
  82. 21 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionOptionsService.cs
  83. 13 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionService.cs
  84. 21 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionSourcewareService.cs
  85. 21 0
      src/Hotline.Application/Exam/Interface/Questions/IQuestionTagService.cs
  86. 20 0
      src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareCategoryService.cs
  87. 19 0
      src/Hotline.Application/Exam/Interface/Sourcewares/ISourcewareService.cs
  88. 13 0
      src/Hotline.Application/Exam/Interface/TestPapers/ITestPaperService.cs
  89. 13 0
      src/Hotline.Application/Exam/Interface/Train/ITrainPlanService.cs
  90. 11 0
      src/Hotline.Application/Exam/Interface/Train/ITrainRecordAnswerService.cs
  91. 20 0
      src/Hotline.Application/Exam/Interface/Train/ITrainRecordService.cs
  92. 13 0
      src/Hotline.Application/Exam/Interface/Train/ITrainTemplateService.cs
  93. 11 0
      src/Hotline.Application/Exam/Mappers/MapperConfigs.cs
  94. 20 0
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/ExamTagQueryExtensions.cs
  95. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionAnswerQueryExtensions.cs
  96. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionKnowladgeQueryExtensions.cs
  97. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionOptionsQueryExtensions.cs
  98. 39 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionQueryExtesions.cs
  99. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionSourcewareQueryExtensions.cs
  100. 20 0
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionTagQueryExtensions.cs

+ 19 - 0
Hotline.Repository.Sqlsugar/Interfaces/IExamTagRepository.cs

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

+ 33 - 0
Hotline.sln

@@ -61,7 +61,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Ai.XingTang", "src\
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hotline.Tests", "test\Hotline.Tests\Hotline.Tests.csproj", "{31855124-4EFC-47B9-A4D5-64822DE036E6}"
 EndProject
+<<<<<<< HEAD
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Pdf", "src\Hotline.Pdf\Hotline.Pdf.csproj", "{3AB75B51-A69D-4145-A564-1D9D1695992E}"
+=======
+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}"
+>>>>>>> feature/exam
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -165,6 +175,22 @@ 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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -198,7 +224,14 @@ 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}
+<<<<<<< HEAD
 		{3AB75B51-A69D-4145-A564-1D9D1695992E} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+=======
+		{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}
+>>>>>>> feature/exam
 	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
+    {
+
+    }
+}

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

@@ -0,0 +1,85 @@
+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("区域")]
+        [JsonIgnore]
+        public string AreaId { get; set; }
+
+        /// <summary>
+        /// 操作状态
+        /// </summary>
+        [Description("操作状态")]
+        public OperationStatus OperationStatus { get; set; }
+    }
+}

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

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+
+namespace Exam.Infrastructure.Data.Entity
+{
+    public class EntityQueryRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 主键集合
+        /// </summary>
+        [Description("主键集合")]
+        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,
+    }
+}

+ 17 - 0
src/01.Infrastructure/Exam.Infrastructure.Validation/Exam.Infrastructure.Validation.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="FluentValidation" Version="11.10.0" />
+  </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);
+            }
+        }
+    }
+}

+ 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
+    {
+
+    }
+}

+ 15 - 0
src/01.Infrastructure/Exam.Infrastructure.Web/Constants/RouteApi.cs

@@ -0,0 +1,15 @@
+namespace Exam.Infrastructure.Web.Constants
+{
+    public class RouteApi
+    {
+        public const string Add = "Add";
+
+        public const string Update = "Update";
+
+        public const string Delete = "Delete";
+
+        public const string GetPagedList = "GetPagedList";
+
+        public const string Get = "Get";
+    }
+}

+ 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="..\..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
+    <ProjectReference Include="..\Exam.Infrastructure\Exam.Infrastructure.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;
+        }
+    }
+}

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

@@ -0,0 +1,64 @@
+using Hotline.Repository.SqlSugar.Interface;
+using Microsoft.Extensions.DependencyInjection;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace Exam.Infrastructure.Web.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), Infrastructure.Utilities.BaseAssemblyUtility.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;
+        }
+    }
+}

+ 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
+    {
+    }
+}

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

@@ -0,0 +1,20 @@
+<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" />
+  </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);
+                }
+            }
+        }
+    }
+}

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

@@ -0,0 +1,23 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using Exam.Insfrastructure.Service.Entitys;
+
+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
+    {
+    }
+}

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

@@ -0,0 +1,55 @@
+using Exam.Infrastructure.Data.Entity;
+using FluentValidation;
+
+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);
+    }
+}

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

@@ -0,0 +1,122 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Insfrastructure.Service.Entitys;
+using Exam.Insfrastructure.Service.Extensions;
+using Exam.Insfrastructure.Service.Interface;
+using FluentValidation;
+using Mapster;
+
+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
+    }
+}

+ 8 - 5
src/Hotline.Api/Controllers/ArticleController.cs

@@ -183,7 +183,7 @@ namespace Hotline.Api.Controllers
                         .ToListAsync(HttpContext.RequestAborted);
                     if (userlist != null && userlist.Count > 0)
                     {
-                        await _circularRecordDomainService.RecordUserHandle(userlist, true);
+                        await _circularRecordDomainService.RecordUserHandle(userlist, true,circular.Title,circular.Content);
                     }
                 }
                 else
@@ -195,7 +195,7 @@ namespace Hotline.Api.Controllers
                         .ToListAsync(HttpContext.RequestAborted);
                     if (orglist != null && orglist.Count > 0)
                     {
-                        await _circularRecordDomainService.RecordOrgHandle(orglist, true);
+                        await _circularRecordDomainService.RecordOrgHandle(orglist, true,circular.Title,circular.Content);
                     }
                 }
             }
@@ -255,6 +255,7 @@ namespace Hotline.Api.Controllers
             circular.SourceOrgId = dto.SourceOrgId;
             circular.SourceOrgName = dto.SourceOrgName;
             circular.NeedReadNum = dto.CircularReadGroups.Count;
+            circular.LostEfficacyTime = DateTime.Now.AddYears(50);
             await _circularRepository.UpdateAsync(circular, HttpContext.RequestAborted);
             //移除子表重新添加
             await _circularReadGroupRepository.RemoveAsync(x => x.CircularId == dto.Id, false, HttpContext.RequestAborted);
@@ -302,6 +303,7 @@ namespace Hotline.Api.Controllers
             model.CircularState = ECircularState.Draft;
             model.ReadedNum = 0;
             model.NeedReadNum = model.CircularReadGroups.Count;
+            model.LostEfficacyTime = DateTime.Now.AddYears(50);
             var id = await _circularRepository.AddAsync(model, HttpContext.RequestAborted);
             //子表
             var list = dto.CircularReadGroups;
@@ -375,7 +377,7 @@ namespace Hotline.Api.Controllers
                     model.ReadedNum++;
                     await _circularRepository.UpdateAsync(model, HttpContext.RequestAborted);
                     //处理计数
-                    await _circularRecordDomainService.RecordUserHandle(new List<string> { _sessionContext.RequiredUserId }, false, cancellationToken: HttpContext.RequestAborted);
+                    await _circularRecordDomainService.RecordUserHandle(new List<string> { _sessionContext.RequiredUserId }, false,"","", cancellationToken: HttpContext.RequestAborted);
                 }
             }
             else
@@ -394,7 +396,7 @@ namespace Hotline.Api.Controllers
                     model.ReadedNum++;
                     await _circularRepository.UpdateAsync(model, HttpContext.RequestAborted);
                     //处理部门计数
-                    await _circularRecordDomainService.RecordOrgHandle(new List<string> { _sessionContext.RequiredOrgId }, false, cancellationToken: HttpContext.RequestAborted);
+                    await _circularRecordDomainService.RecordOrgHandle(new List<string> { _sessionContext.RequiredOrgId }, false,"","", cancellationToken: HttpContext.RequestAborted);
                 }
             }
 
@@ -542,7 +544,7 @@ namespace Hotline.Api.Controllers
             bulletin.Content = dto.Content;
             bulletin.BulletinTypeId = dto.BulletinTypeId;
             bulletin.BulletinTypeName = dto.BulletinTypeName;
-            bulletin.LoseEfficacyTime = dto.LoseEfficacyTime;
+            bulletin.LoseEfficacyTime = DateTime.Now.AddYears(50);
             bulletin.PushRanges = dto.PushRanges;
             bulletin.DisplayLocation = dto.DisplayLocation;
             bulletin.SourceOrgId = dto.SourceOrgId;
@@ -581,6 +583,7 @@ namespace Hotline.Api.Controllers
             model.BulletinState = Share.Enums.Article.EBulletinState.Draft;
             model.ReadedNum = 0;
             model.IsArrive = false;
+            model.LoseEfficacyTime = DateTime.Now.AddYears(50);
             await _bulletinRepository.AddAsync(model, HttpContext.RequestAborted);
         }
 

+ 194 - 24
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -50,6 +50,7 @@ using Hotline.KnowledgeBase;
 using DocumentFormat.OpenXml.Vml.Spreadsheet;
 using Hotline.Share.Tools;
 using MediatR;
+using static Hotline.AppDefaults;
 
 namespace Hotline.Api.Controllers.Bi
 {
@@ -1148,7 +1149,7 @@ namespace Hotline.Api.Controllers.Bi
         {
             var IsCenter = _sessionContext.OrgIsCenter;
             var (hotSpot, data) = await _orderRepository.HotPortJoinOrgStatistics(dto.StartTime, dto.EndTime, IsCenter, _sessionContext.OrgId);
-			return  new { hotSpot, data };
+            return new { hotSpot, data };
         }
 
 
@@ -2185,7 +2186,7 @@ namespace Hotline.Api.Controllers.Bi
                 })
                 .FirstAsync();
 
-            if (listInfo != null && listInfo.Rows.Count > 0 && orderAgingData != null)
+			if (listInfo != null && listInfo.Rows.Count > 0 && orderAgingData != null)
             {
                 orderAgingData.OrderCount = orderAgingData.OrderCount + Convert.ToInt32(listInfo.Rows[0]["AllCount"]);
                 orderAgingData.CompletedAging = orderAgingData.CompletedAging + Convert.ToInt32(listInfo.Rows[0]["OrderWorkTime"]);
@@ -2635,7 +2636,7 @@ namespace Hotline.Api.Controllers.Bi
                 })
                 .FirstAsync();
 
-            enterpriseOrderDto.InProgressCount = enterpriseOrderData.InProgressCount;
+			enterpriseOrderDto.InProgressCount = enterpriseOrderData.InProgressCount;
             enterpriseOrderDto.CompletedCount = enterpriseOrderData.CompletedCount;
             enterpriseOrderDto.CenterCount = enterpriseOrderData.CenterCount;
             enterpriseOrderDto.CityCount = enterpriseOrderData.CityCount;
@@ -2646,27 +2647,27 @@ namespace Hotline.Api.Controllers.Bi
                 .LeftJoin<SystemOrganize>((it, so) => it.VisitOrgCode == so.Id)
                 .Where((it, so) => it.VisitTarget == EVisitTarget.Org && it.OrderVisit.VisitTime >= StartTime && it.OrderVisit.VisitTime <= EndTime && it.OrderVisit.VisitState == EVisitState.Visited && it.OrderVisit.Order.IdentityType == EIdentityType.Enterprise)
                 .GroupBy((it, so) => it.Id)
-                 .Select((it, so) => new EnterpriseOrderDto
+                .Select((it, so) => new EnterpriseOrderDto
                  {
-                     VisitdCount = SqlFunc.AggregateSum(SqlFunc.IIF(true, 1, 0)),
-                     Dissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2"), 1, 0)),
+                     VisitdCount = SqlFunc.AggregateCount(1),
+					 Dissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2"), 1, 0)),
                      Satisfied = SqlFunc.AggregateSum(SqlFunc.IIF(!SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") && !SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2"), 1, 0)),
                      CityDissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF((SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2")) && so.OrgType == EOrgType.City, 1, 0)),
                      CitySatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(!SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") && !SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2") && so.OrgType == EOrgType.City, 1, 0)),
                      CountyDissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF((SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2")) && so.OrgType == EOrgType.County, 1, 0)),
                      CountySatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(!SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") && !SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2") && so.OrgType == EOrgType.County, 1, 0)),
                  })
-                .FirstAsync();
+                .ToListAsync();
 
-            if (enterpriseOrderDto2 != null)
+			if (enterpriseOrderDto2.Any())
             {
-                centerReportStatisticsDto.EnterpriseOrderDto.VisitdCount = enterpriseOrderDto2.VisitdCount;
-                centerReportStatisticsDto.EnterpriseOrderDto.Dissatisfied = enterpriseOrderDto2.Dissatisfied;
-                centerReportStatisticsDto.EnterpriseOrderDto.CityDissatisfied = enterpriseOrderDto2.CityDissatisfied;
-                centerReportStatisticsDto.EnterpriseOrderDto.CountyDissatisfied = enterpriseOrderDto2.CountyDissatisfied;
-                centerReportStatisticsDto.EnterpriseOrderDto.Satisfied = enterpriseOrderDto2.Satisfied;
-                centerReportStatisticsDto.EnterpriseOrderDto.CitySatisfied = enterpriseOrderDto2.CitySatisfied;
-                centerReportStatisticsDto.EnterpriseOrderDto.CountySatisfied = enterpriseOrderDto2.CountySatisfied;
+                centerReportStatisticsDto.EnterpriseOrderDto.VisitdCount = enterpriseOrderDto2.Sum(x=>x.VisitdCount);
+                centerReportStatisticsDto.EnterpriseOrderDto.Dissatisfied = enterpriseOrderDto2.Sum(x => x.Dissatisfied);
+                centerReportStatisticsDto.EnterpriseOrderDto.CityDissatisfied = enterpriseOrderDto2.Sum(x => x.CityDissatisfied);
+                centerReportStatisticsDto.EnterpriseOrderDto.CountyDissatisfied = enterpriseOrderDto2.Sum(x => x.CountyDissatisfied);
+                centerReportStatisticsDto.EnterpriseOrderDto.Satisfied = enterpriseOrderDto2.Sum(x => x.Satisfied);
+                centerReportStatisticsDto.EnterpriseOrderDto.CitySatisfied = enterpriseOrderDto2.Sum(x => x.CitySatisfied);
+                centerReportStatisticsDto.EnterpriseOrderDto.CountySatisfied = enterpriseOrderDto2.Sum(x => x.CountySatisfied);
             }
 
             if (listInfo != null && listInfo.Rows.Count > 0 && centerReportStatisticsDto.EnterpriseOrderDto != null)
@@ -4090,7 +4091,7 @@ namespace Hotline.Api.Controllers.Bi
             //总计
             var total = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "总计",
                 TotalSumCount = list.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = list.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4108,7 +4109,7 @@ namespace Hotline.Api.Controllers.Bi
             var countyList = list.Where(x => x.OrgType == EOrgType.County).ToList();
             var countyTotal = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "区县合计",
                 TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4127,7 +4128,7 @@ namespace Hotline.Api.Controllers.Bi
             var cityList = list.Where(x => x.OrgType == EOrgType.City).ToList();
             var cityTotal = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "市直合计",
                 TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4157,7 +4158,7 @@ namespace Hotline.Api.Controllers.Bi
             //总计
             var total = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "总计",
                 TotalSumCount = secondaryHandling.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = secondaryHandling.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4173,7 +4174,7 @@ namespace Hotline.Api.Controllers.Bi
             var countyList = secondaryHandling.Where(x => x.OrgType == EOrgType.County).ToList();
             var countyTotal = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "区县合计",
                 TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4189,7 +4190,7 @@ namespace Hotline.Api.Controllers.Bi
             var cityList = secondaryHandling.Where(x => x.OrgType == EOrgType.City).ToList();
             var cityTotal = new SecondaryHandlingSatisfactionVo
             {
-                OrgId = "0",
+                OrgId = "",
                 OrgName = "市直合计",
                 TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
                 VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
@@ -4593,7 +4594,7 @@ namespace Hotline.Api.Controllers.Bi
         {
             //var query = _orderReportApplication.OrgVisitDetailList(dto);
             var query = _orderRepository.OrgVisitDetailList(dto);
-            Console.Write(query.ToSqlString());
+            var sql = query.ToSqlString();
             var (total, items) = await query.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
             return new PagedDto<OrgVisitDetailListResp>(total, _mapper.Map<IReadOnlyList<OrgVisitDetailListResp>>(items));
@@ -4629,7 +4630,8 @@ namespace Hotline.Api.Controllers.Bi
                 { "acceptTypeOptions",_sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType)},
                 { "orderStatusOptions", EnumExts.GetDescriptions<EOrderStatus>()},
                 { "currentStepOptions",definition?.Steps.Select(x => new Kv(x.Code, x.Name))},
-                 { "visitMananer", _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner) },
+                { "visitMananer", _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner) },
+                { "orderTags", _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.OrderTag) }
         };
         }
 
@@ -5858,5 +5860,173 @@ namespace Hotline.Api.Controllers.Bi
                    })
                    .ToListAsync(HttpContext.RequestAborted);
         }
-    }
+
+		/// <summary>
+		/// 超期退回统计
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet("extended_sendback_statistics")]
+		public async Task<List<ExtendedSendBackVo>> ExtendedSendBackStatistics([FromQuery] PagedKeywordRequest dto)
+		{
+			var quer = _orderApplication.ExtendedSendBackStatistics(dto);
+            var list = await quer.ToListAsync(HttpContext.RequestAborted);
+			list.Add(new ExtendedSendBackVo()
+			{
+                OrgName ="合计",
+				TotalNum = list.Sum(m => m.TotalNum),
+				PassNum = list.Sum(p => p.PassNum),
+				NoPassNum = list.Sum(m => m.NoPassNum),
+				AuditNum = list.Sum(m => m.AuditNum)
+			});
+
+			return list;
+		}
+
+		/// <summary>
+		/// 超期退回统计导出
+		/// </summary>
+		/// <returns></returns>
+		[HttpPost("extended_sendback_statistics/_export")]
+		public async Task<FileStreamResult> ExtendedSendBackStatisticsExport([FromBody] ExportExcelDto<PagedKeywordRequest> dto)
+		{
+			var query = _orderApplication.ExtendedSendBackStatistics(dto.QueryDto);
+			List<ExtendedSendBackVo> data;
+
+			data = await query.ToListAsync(HttpContext.RequestAborted);
+
+			data.Add(new ExtendedSendBackVo()
+			{
+				OrgName = "合计",
+				TotalNum = data.Sum(m => m.TotalNum),
+				PassNum = data.Sum(p => p.PassNum),
+				NoPassNum = data.Sum(m => m.NoPassNum),
+				AuditNum = data.Sum(m => m.AuditNum)
+			});
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+			var dtos = data
+				.Select(stu => _mapper.Map(stu, typeof(ExtendedSendBackVo), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "超期退回统计");
+		}
+
+		/// <summary>
+		/// 超期退回统计明细
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet("extended_sendback_detail")]
+		public async Task<PagedDto<SendBackDto>> ExtendedSendBackDetail([FromQuery] ExtendedSendBackRequest dto)
+		{
+			var quer = _orderApplication.ExtendedSendBackDetail(dto);
+			var (total, items) = await quer.ToPagedListAsync(dto.PageIndex, dto.PageSize);
+
+			return new PagedDto<SendBackDto>(total, _mapper.Map<IReadOnlyList<SendBackDto>>(items));
+		}
+
+		/// <summary>
+		/// 超期退回统计明细导出
+		/// </summary>
+		/// <returns></returns>
+		[HttpPost("extended_sendback_detail/_export")]
+		public async Task<FileStreamResult> ExtendedSendBackDetailExport([FromBody] ExportExcelDto<ExtendedSendBackRequest> dto)
+		{
+			var query = _orderApplication.ExtendedSendBackDetail(dto.QueryDto);
+			List<OrderSendBackAudit> data;
+			if (dto.IsExportAll)
+			{
+				data = await query.ToListAsync(HttpContext.RequestAborted);
+			}
+			else
+			{
+				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+				data = items;
+			}
+
+			var dataDtos = _mapper.Map<ICollection<SendBackDto>>(data);
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+			var dtos = dataDtos
+				.Select(stu => _mapper.Map(stu, typeof(SendBackDto), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "超期退回统计明细");
+		}
+
+		/// <summary>
+		/// 列表页面基础数据
+		/// </summary>
+		/// <returns></returns>
+		[HttpGet("extended_sendback_detail/base-data")]
+		public async Task<object> ExtendedSendBackDetailBaseData()
+		{
+			var rsp = new
+			{
+				AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
+				ChannelOptions = _sysDicDataCacheManager.GetSysDicDataCache(TimeLimitBaseDataConsts.SourceChannel),
+				SendBackAuditState = EnumExts.GetDescriptions<ESendBackAuditState>()
+			};
+			return rsp;
+		}
+
+        /// <summary>
+        /// 网上群众路线通报
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("onlinemassline-report")]
+        public async Task<List<OnlineMassLineReportRes>> OnlineMassLineReport([FromQuery] OnlineMassLineReportRequest dto)
+        {
+            var nowList = await _orderRepository.Queryable()
+                .Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime).ToListAsync();
+                
+
+            var oldList =await _orderRepository.Queryable()
+                .Where(x => x.CreationTime >= dto.ChainStartTime && x.CreationTime <= dto.ChainEndTime).ToListAsync();
+
+
+            var returnList = new List<OnlineMassLineReportRes>();
+            //市12345政务服务便民热线
+            var Other = new OnlineMassLineReportRes();
+            Other.SourceChannel = "市12345政务服务便民热线";
+            Other.SumCount = nowList.Where(x => x.IsProvince==false && x.SourceChannelCode != "SZYSM").Count(); //市12345政务服务便民热线
+            Other.PreviousCount = oldList.Where(x => x.IsProvince == false && x.SourceChannelCode != "SZYSM").Count(); //市12345政务服务便民热线
+            returnList.Add(Other);
+            //中国政府网“我向总理说句话”
+            var ZGZFW = new OnlineMassLineReportRes();
+            ZGZFW.SourceChannel = "中国政府网“我向总理说句话”";
+            ZGZFW.SumCount = nowList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("ZGZFW")).Count(); //中国政府网“我向总理说句话”
+            ZGZFW.PreviousCount = oldList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("ZGZFW")).Count(); //中国政府网“我向总理说句话”
+            returnList.Add(ZGZFW);
+            //国家政务服务平台“投诉与建议”
+            var GJZWFWPT = new OnlineMassLineReportRes();
+            GJZWFWPT.SourceChannel = "国家政务服务平台“投诉与建议”";
+            GJZWFWPT.SumCount = nowList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("GJZWFWPT")).Count(); //国家政务服务平台“投诉与建议”
+            GJZWFWPT.PreviousCount = oldList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("GJZWFWPT")).Count(); //国家政务服务平台“投诉与建议”
+            returnList.Add(GJZWFWPT);
+            //省12345政务服务便民热线
+            var Province12345 = new OnlineMassLineReportRes();
+            Province12345.SourceChannel = "省12345政务服务便民热线";
+            Province12345.SumCount = nowList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("GJZWFWPT")==false && x.ReceiveProvinceNo.StartsWith("ZGZFW")==false).Count(); //国家政务服务平台“投诉与建议”
+            Province12345.PreviousCount = oldList.Where(x => x.IsProvince == true && x.ReceiveProvinceNo.StartsWith("GJZWFWPT")==false && x.ReceiveProvinceNo.StartsWith("ZGZFW")==false).Count(); //国家政务服务平台“投诉与建议”
+            returnList.Add(Province12345);
+            //市政府网站“市长与网民”
+            var SZYSM = new OnlineMassLineReportRes();
+            SZYSM.SourceChannel = "市政府网站“市长与网民”";
+            SZYSM.SumCount = nowList.Where(x => x.SourceChannelCode=="SZYSM").Count(); //国家政务服务平台“投诉与建议”
+            SZYSM.PreviousCount = oldList.Where(x => x.SourceChannelCode == "SZYSM").Count(); //国家政务服务平台“投诉与建议”
+            returnList.Add(SZYSM);
+            return returnList;
+        }
+	}
 }

+ 155 - 0
src/Hotline.Api/Controllers/EnterpriseLuzhouController.cs

@@ -0,0 +1,155 @@
+using Hotline.Enterprise;
+using Hotline.Share.Dtos.Enterprise;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Users;
+using Hotline.Share.Dtos;
+using Hotline.Tools;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using Hotline.Application.Enterprise;
+using Hotline.Configurations;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
+using MapsterMapper;
+using Microsoft.Extensions.Options;
+using XF.Domain.Repository;
+
+namespace Hotline.Api.Controllers
+{
+    /// <summary>
+    /// 泸州企业专员工单管理
+    /// </summary>
+    public class EnterpriseLuzhouController : BaseController
+    {
+        private readonly IRepository<EnterpriseSpecialist> _enterpriseSpecialistRepository;
+        private readonly IEnterpriseApplication _enterpriseApplication;
+        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+
+        public EnterpriseLuzhouController(
+            IRepository<EnterpriseSpecialist> enterpriseSpecialistRepository,
+            IEnterpriseApplication enterpriseApplication,
+            IOptionsSnapshot<AppConfiguration> appOptions,
+            ISessionContext sessionContext,
+            IMapper mapper)
+        {
+            _enterpriseSpecialistRepository = enterpriseSpecialistRepository;
+            _enterpriseApplication = enterpriseApplication;
+            _appOptions = appOptions;
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+        }
+
+        
+        [HttpGet("specialist/users")]
+        public async Task<PagedDto<UserDto>> QueryUsers([FromQuery] UserPagedDto request)
+        {
+            var query = _enterpriseApplication.QueryUsersForSpecialist(request);
+            var (total, users) = await query.ToPagedListAsync(request);
+            var userDtos = _mapper.Map<List<UserDto>>(users);
+            return new PagedDto<UserDto>(total, userDtos);
+        }
+
+        /// <summary>
+        /// 批量新增企业专员
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("specialist-batch")]
+        public async Task AddSpecialists([FromBody] List<AddEnterpriseSpecialistDto> dto)
+        {
+            var specialists = _mapper.Map<List<EnterpriseSpecialist>>(dto);
+            await _enterpriseSpecialistRepository.AddRangeAsync(specialists, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 批量删除企业专员
+        /// </summary>
+        [HttpDelete("specialist-batch")]
+        public async Task RemoveSpecialists([FromBody] List<string> ids)
+        {
+            var specialists = await _enterpriseSpecialistRepository.Queryable()
+                .Where(d => ids.Contains(d.Id) || ids.Contains(d.ParentId))
+                .ToListAsync(HttpContext.RequestAborted);
+            await _enterpriseSpecialistRepository.RemoveRangeAsync(specialists, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 查询企业专员
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("specialists-tree")]
+        public Task<List<EnterpriseSpecialistDto>> QuerySpecialists()
+        {
+            return _enterpriseSpecialistRepository.Queryable()
+                .Includes(d => d.User, s => s.Account)
+                .OrderBy(d => d.Id)
+                .Select(d => new EnterpriseSpecialistDto
+                {
+                    Id = d.Id,
+                    ParentId = d.ParentId,
+                    Name = d.User.Name,
+                    UserName = d.User.Account.UserName,
+                    PhoneNo = d.User.PhoneNo,
+                })
+                .ToTreeAsync(d => d.Children, it => it.ParentId, null);
+        }
+
+        /// <summary>
+        /// 查询企业专员工单
+        /// </summary>
+        [HttpGet("specialist/orders")]
+        public async Task<IReadOnlyList<OrderDto>> QueryEnterpriseSpecialistOrders([FromQuery] QueryOrderDto dto)
+        {
+            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto, HttpContext.RequestAborted);
+            var orders = await query.ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
+            return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
+        }
+
+        /// <summary>
+        /// 查询总数
+        /// </summary>
+        [HttpGet("specialist/orders/count")]
+        public async Task<int> CountEnterpriseSpecialistOrders([FromQuery] QueryOrderDto dto)
+        {
+            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto, HttpContext.RequestAborted);
+            return await query.CountAsync(HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 导出企业专员工单
+        /// </summary>
+        [HttpPost("specialist/orders/export")]
+        public async Task<FileStreamResult> ExportOrders([FromBody] ExportExcelDto<QueryOrderDto> dto)
+        {
+            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto.QueryDto, HttpContext.RequestAborted);
+            List<Order> orders;
+            if (dto.IsExportAll)
+            {
+                orders = await query.ToListAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+                orders = items;
+            }
+
+            var orderDtos = _mapper.Map<ICollection<OrderDto>>(orders);
+
+            if (_appOptions.Value.IsLuZhou && !_sessionContext.OrgIsCenter)
+                orderDtos = orderDtos.Select(p => p.DataMask()).ToList();
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+            var dtos = orderDtos
+                .Select(stu => _mapper.Map(stu, typeof(OrderDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "企业专员工单数据");
+        }
+
+    }
+}

+ 1 - 130
src/Hotline.Api/Controllers/EnterprisesController.cs

@@ -21,26 +21,10 @@ namespace Hotline.Api.Controllers
     public class EnterprisesController : BaseController
     {
         private readonly IEnterpriseService _enterpriseService;
-        private readonly IRepository<EnterpriseSpecialist> _enterpriseSpecialistRepository;
-        private readonly IEnterpriseApplication _enterpriseApplication;
-        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
-        private readonly ISessionContext _sessionContext;
-        private readonly IMapper _mapper;
 
-        public EnterprisesController(
-            IEnterpriseService enterpriseService,
-            IRepository<EnterpriseSpecialist> enterpriseSpecialistRepository,
-            IEnterpriseApplication enterpriseApplication,
-            IOptionsSnapshot<AppConfiguration> appOptions,
-            ISessionContext sessionContext,
-            IMapper mapper)
+        public EnterprisesController(IEnterpriseService enterpriseService)
         {
             _enterpriseService = enterpriseService;
-            _enterpriseSpecialistRepository = enterpriseSpecialistRepository;
-            _enterpriseApplication = enterpriseApplication;
-            _appOptions = appOptions;
-            _sessionContext = sessionContext;
-            _mapper = mapper;
         }
 
         #region (宜宾)获取企业信息
@@ -58,118 +42,5 @@ namespace Hotline.Api.Controllers
         }
 
         #endregion
-
-        #region 泸州
-
-        [HttpGet("specialist/users")]
-        public async Task<PagedDto<UserDto>> QueryUsers([FromQuery] UserPagedDto request)
-        {
-            var query = _enterpriseApplication.QueryUsersForSpecialist(request);
-            var (total, users) = await query.ToPagedListAsync(request);
-            var userDtos = _mapper.Map<List<UserDto>>(users);
-            return new PagedDto<UserDto>(total, userDtos);
-        }
-
-        /// <summary>
-        /// 批量新增企业专员
-        /// </summary>
-        /// <returns></returns>
-        [HttpPost("specialist-batch")]
-        public async Task AddSpecialists([FromBody] List<AddEnterpriseSpecialistDto> dto)
-        {
-            var specialists = _mapper.Map<List<EnterpriseSpecialist>>(dto);
-            await _enterpriseSpecialistRepository.AddRangeAsync(specialists, HttpContext.RequestAborted);
-        }
-
-        /// <summary>
-        /// 批量删除企业专员
-        /// </summary>
-        [HttpDelete("specialist-batch")]
-        public async Task RemoveSpecialists([FromBody] List<string> ids)
-        {
-            var specialists = await _enterpriseSpecialistRepository.Queryable()
-                .Where(d => ids.Contains(d.Id) || ids.Contains(d.ParentId))
-                .ToListAsync(HttpContext.RequestAborted);
-            await _enterpriseSpecialistRepository.RemoveRangeAsync(specialists, HttpContext.RequestAborted);
-        }
-
-        /// <summary>
-        /// 查询企业专员
-        /// </summary>
-        /// <returns></returns>
-        [HttpGet("specialists-tree")]
-        public Task<List<EnterpriseSpecialistDto>> QuerySpecialists()
-        {
-            return _enterpriseSpecialistRepository.Queryable()
-                .Includes(d => d.User, s => s.Account)
-                .OrderBy(d => d.Id)
-                .Select(d=>new EnterpriseSpecialistDto
-                {
-                    Id = d.Id,
-                    ParentId = d.ParentId,
-                    Name = d.User.Name,
-                    UserName = d.User.Account.UserName,
-                    PhoneNo = d.User.PhoneNo,
-                })
-                .ToTreeAsync(d => d.Children, it => it.ParentId, null);
-        }
-
-        /// <summary>
-        /// 查询企业专员工单
-        /// </summary>
-        [HttpGet("specialist/orders")]
-        public async Task<IReadOnlyList<OrderDto>> QueryEnterpriseSpecialistOrders([FromQuery] QueryOrderDto dto)
-        {
-            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto, HttpContext.RequestAborted);
-            var orders = await query.ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
-            return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
-        }
-
-        /// <summary>
-        /// 查询总数
-        /// </summary>
-        [HttpGet("specialist/orders/count")]
-        public async Task<int> CountEnterpriseSpecialistOrders([FromQuery] QueryOrderDto dto)
-        {
-            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto, HttpContext.RequestAborted);
-            return await query.CountAsync(HttpContext.RequestAborted);
-        }
-
-        /// <summary>
-        /// 导出企业专员工单
-        /// </summary>
-        [HttpPost("specialist/orders/export")]
-        public async Task<FileStreamResult> ExportOrders([FromBody] ExportExcelDto<QueryOrderDto> dto)
-        {
-            var query = await _enterpriseApplication.QueryEnterpriseSpecialistOrdersAsync(dto.QueryDto, HttpContext.RequestAborted);
-            List<Order> orders;
-            if (dto.IsExportAll)
-            {
-                orders = await query.ToListAsync(HttpContext.RequestAborted);
-            }
-            else
-            {
-                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-                orders = items;
-            }
-
-            var orderDtos = _mapper.Map<ICollection<OrderDto>>(orders);
-
-            if (_appOptions.Value.IsLuZhou && !_sessionContext.OrgIsCenter)
-                orderDtos = orderDtos.Select(p => p.DataMask()).ToList();
-
-            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-
-            var dtos = orderDtos
-                .Select(stu => _mapper.Map(stu, typeof(OrderDto), dynamicClass))
-                .Cast<object>()
-                .ToList();
-
-            var stream = ExcelHelper.CreateStream(dtos);
-
-            return ExcelStreamResult(stream, "企业专员工单数据");
-        }
-
-        #endregion
     }
 }

+ 23 - 0
src/Hotline.Api/Controllers/Exam/QuestionController.cs

@@ -0,0 +1,23 @@
+using Exam.Application.Interface.Questions;
+using Hotline.Share.Dtos.Questions;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class QuestionController : BaseController
+    {
+        private readonly IQuestionService _questionService;
+        public QuestionController(IQuestionService questionService)
+        {
+            _questionService = questionService;
+        }
+
+        [HttpPost]
+        [AllowAnonymous]
+        public async Task Add([FromBody] QuestionDto questionDto)
+        {
+            await _questionService.AddAsync(questionDto, HttpContext.RequestAborted);
+        }
+    }
+}

+ 89 - 0
src/Hotline.Api/Controllers/Exam/SourcewareCategoryController.cs

@@ -0,0 +1,89 @@
+using Exam.Application;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Extensions;
+using Exam.Share.ViewResponses.Sourceware;
+using Hotline.Application.Exam.Constants;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Sourcewares;
+using Hotline.Share.Dtos.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class SourcewareCategoryController : BaseController
+    {
+        private ISourcewareCategoryService _sourcewareCategoryService;
+        private ISessionContext _sessionContext;
+        public SourcewareCategoryController(ISourcewareCategoryService sourcewareCategoryService,ISessionContext sessionContext)
+        {
+            _sourcewareCategoryService = sourcewareCategoryService;
+            _sessionContext = sessionContext;
+        }
+
+        /// <summary>
+        /// 新增课件类型
+        /// </summary>
+        /// <param name="sourcewareCategoryDto"></param>
+        /// <returns></returns>
+        [HttpPost(SourcewareCategoryRouteApi.Add)]
+        public async Task Add([FromBody] SourcewareCategoryDto sourcewareCategoryDto)
+        {
+            sourcewareCategoryDto.InitRequest(_sessionContext);
+            await _sourcewareCategoryService.AddAsync(sourcewareCategoryDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 修改课件类型
+        /// </summary>
+        /// <param name="sourcewareCategoryDto"></param>
+        /// <returns></returns>
+        [HttpPut(SourcewareCategoryRouteApi.Update)]
+        public async Task Update([FromBody] SourcewareCategoryDto sourcewareCategoryDto)
+        {
+            sourcewareCategoryDto.InitRequest(_sessionContext);
+            await _sourcewareCategoryService.UpdateAsync(sourcewareCategoryDto,HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 删除课件类型
+        /// </summary>
+        /// <param name="entityQueryRequest"></param>
+        /// <returns></returns>
+        [HttpDelete(SourcewareCategoryRouteApi.Delete)]
+
+        public async Task Delete(EntityQueryRequest entityQueryRequest)
+        {
+            await _sourcewareCategoryService.DeleteAsync(entityQueryRequest, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 获取树形列表
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost(SourcewareCategoryRouteApi.GetTreeList)]
+        public async Task<List<SourcewareCategoryViewResponse>> GetTreeList([FromBody] SourcewareCategoryRequest sourcewareCategoryRequest)
+        {
+            var sourcewareCategorys = await _sourcewareCategoryService.GetTreeAsync(sourcewareCategoryRequest);
+
+            return sourcewareCategorys;
+        }
+
+        /// <summary>
+        /// 获取课件分类
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet(SourcewareCategoryRouteApi.Get)]
+        public async Task<SourcewareCategoryDto> Get(string id)
+        {
+            var sourcewareCategoryDto = await _sourcewareCategoryService.GetAsync(new EntityQueryRequest
+            {
+                Id =id
+            });
+
+            return sourcewareCategoryDto;
+        }
+
+    }
+}

+ 88 - 0
src/Hotline.Api/Controllers/Exam/SourcewaresController.cs

@@ -0,0 +1,88 @@
+using Exam.Application.Interface.Sourcewares;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Extensions;
+using Hotline.Application.Exam.Constants;
+using Hotline.Share.Dtos.Sourcewares;
+using Hotline.Share.Requests.Sourceware;
+using Hotline.Share.ViewResponses.Sourcewares;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class SourcewaresController : BaseController
+    {
+        private ISourcewareService _sourcewareService;
+        private ISessionContext _sessionContext;
+
+        public SourcewaresController(ISourcewareService sourcewareService,ISessionContext sessionContext)
+        {
+            _sourcewareService = sourcewareService;
+            _sessionContext = sessionContext;
+        }
+
+        /// <summary>
+        /// 新增课件
+        /// </summary>
+        /// <param name="sourcewareDto"></param>
+        /// <returns></returns>
+        [HttpPost(SourcewareRouteApi.Add)]
+        public async Task Add([FromBody] SourcewareDto sourcewareDto)
+        {
+            sourcewareDto.InitRequest(_sessionContext);
+            await _sourcewareService.AddAsync(sourcewareDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 修改课件
+        /// </summary>
+        /// <param name="sourcewareDto"></param>
+        /// <returns></returns>
+        [HttpPut(SourcewareRouteApi.Update)]
+        public async Task Update([FromBody] SourcewareDto sourcewareDto)
+        {
+            sourcewareDto.InitRequest(_sessionContext);
+            await _sourcewareService.UpdateAsync(sourcewareDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 删除课件
+        /// </summary>
+        /// <param name="entityQueryRequest"></param>
+        /// <returns></returns>
+        [HttpDelete(SourcewareRouteApi.Delete)]
+        public async Task Delete([FromBody] EntityQueryRequest entityQueryRequest)
+        {
+            await _sourcewareService.DeleteAsync(entityQueryRequest, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 获取课件分页列表
+        /// </summary>
+        /// <param name="sourcewarePagedRequest"></param>
+        /// <returns></returns>
+        [HttpPost(SourcewareRouteApi.GetPagedList)]
+        public async Task<SourcewarePageViewResponse> GetPagedList([FromBody] SourcewarePagedRequest sourcewarePagedRequest)
+        {
+            var sourcewarePageViewResponse = await _sourcewareService.GetPagedListAsync(sourcewarePagedRequest);
+
+            return sourcewarePageViewResponse as SourcewarePageViewResponse; 
+        }
+
+        /// <summary>
+        /// 获取课件
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet(SourcewareRouteApi.Get)]
+        public async Task<SourcewareDto> Get(string id)
+        {
+            var sourcewareDto = await _sourcewareService.GetAsync(new EntityQueryRequest
+            {
+                Id = id
+            });
+
+            return sourcewareDto;
+        }
+    }
+}

+ 777 - 0
src/Hotline.Api/Controllers/FwThirdController.cs

@@ -0,0 +1,777 @@
+using DocumentFormat.OpenXml.Spreadsheet;
+using Hotline.Application.Bulletin;
+using Hotline.Application.Identity;
+using Hotline.Application.Orders;
+using Hotline.Article;
+using Hotline.Caching.Interfaces;
+using Hotline.Configurations;
+using Hotline.KnowledgeBase;
+using Hotline.Orders;
+using Hotline.Push.Notifies;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Repository.SqlSugar.Knowledge;
+using Hotline.Settings;
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
+using Hotline.Share.Dtos.Identity;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Schedulings;
+using Hotline.Share.Dtos.WebPortal;
+using Hotline.Share.Enums.KnowledgeBase;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Push;
+using Hotline.Share.Tools;
+using Hotline.Tools;
+using Hotline.WebPortal;
+using Hotline.YbEnterprise.Sdk;
+using MapsterMapper;
+using MathNet.Numerics;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using MongoDB.Driver.Core.WireProtocol.Messages;
+using Newtonsoft.Json.Linq;
+using Senparc.Weixin.WxOpen.AdvancedAPIs.WxApp.WxAppJson;
+using Senparc.Weixin.WxOpen.Entities;
+using SharpCompress.Common;
+using SqlSugar;
+using System.Text.Json;
+using System.Threading;
+using XF.Domain.Authentications;
+using XF.Domain.Cache;
+using XF.Domain.Filters;
+using XF.Domain.Repository;
+
+namespace Hotline.Api.Controllers
+{
+    public class FwThirdController : BaseController
+    {
+        #region 注入
+
+        private readonly IMapper _mapper;
+        private readonly IMediator _mediator;
+        private readonly IRepository<Bulletin> _bulletinRepository;
+        private readonly IRepository<WebUserRegister> _webUserRegisterRepository;
+        private readonly IRepository<WebUserAuth> _webUserAuthRepository;
+        private readonly IRepository<WebFlowAccept> _webFlowAcceptRepository;
+        private readonly ITypedCache<WriteLettersSendSmsDto> _writeLettersSendSms;
+        private readonly IRepository<Hotspot> _hotspotRepository;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<OrderPublish> _orderPublishRepository;
+        private readonly IOrderRepository _orderRepository;
+        private readonly IRepository<OrderVisit> _orderVisitRepository;
+        private readonly IOrderApplication _orderApplication;
+        private readonly ISessionContext _sessionContext;
+        private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+        private readonly IBulletinApplication _bulletinApplication;
+        private readonly IRepository<OldPublicData> _oldPublicDataRepository;
+        private readonly IRepository<KnowledgeType> _knowledgeTypeRepository;
+        private readonly IRepository<Knowledge> _knowledgeRepository;
+        private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly ITypedCache<string> _getVailData;
+        private readonly IRepository<KnowledgeWord> _knowledgeWordRepository;
+        private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
+        private readonly IIdentityAppService _identityAppService;
+        private readonly IHttpClientFactory _httpClientFactory;
+
+        public FwThirdController(IMapper mapper,
+            IMediator mediator,
+            IRepository<Bulletin> bulletinRepository,
+            IRepository<WebUserRegister> webUserRegisterRepository,
+            IRepository<WebUserAuth> webUserAuthRepository,
+            IRepository<WebFlowAccept> webFlowAcceptRepository,
+            ITypedCache<WriteLettersSendSmsDto> writeLettersSendSms,
+            IRepository<Hotspot> hotspotRepository,
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IRepository<OrderPublish> orderPublishRepository,
+            IOrderRepository orderRepository,
+            IRepository<OrderVisit> orderVisitRepository,
+            IOrderApplication orderApplication,
+            ISessionContext sessionContext,
+            IRepository<OrderVisitDetail> orderVisitDetailRepository,
+            IBulletinApplication bulletinApplication,
+            IRepository<OldPublicData> oldPublicDataRepository,
+            IRepository<KnowledgeType> knowledgeTypeRepository,
+            IRepository<Knowledge> knowledgeRepository,
+            ISystemDicDataCacheManager systemDicDataCacheManager,
+            IOptionsSnapshot<AppConfiguration> appOptions,
+            ITypedCache<string> getVailData,
+            IRepository<KnowledgeWord> knowledgeWordRepository,
+            ISystemDicDataCacheManager sysDicDataCacheManager,
+            IIdentityAppService identityAppService,
+            IHttpClientFactory httpClientFactory
+            )
+        {
+            _mapper = mapper;
+            _mediator = mediator;
+            _bulletinRepository = bulletinRepository;
+            _webUserRegisterRepository = webUserRegisterRepository;
+            _webUserAuthRepository = webUserAuthRepository;
+            _webFlowAcceptRepository = webFlowAcceptRepository;
+            _writeLettersSendSms = writeLettersSendSms;
+            _hotspotRepository = hotspotRepository;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _orderPublishRepository = orderPublishRepository;
+            _orderRepository = orderRepository;
+            _orderVisitRepository = orderVisitRepository;
+            _orderApplication = orderApplication;
+            _sessionContext = sessionContext;
+            _orderVisitDetailRepository = orderVisitDetailRepository;
+            _bulletinApplication = bulletinApplication;
+            _oldPublicDataRepository = oldPublicDataRepository;
+            _knowledgeTypeRepository = knowledgeTypeRepository;
+            _knowledgeRepository = knowledgeRepository;
+            _systemDicDataCacheManager = systemDicDataCacheManager;
+            _appOptions = appOptions;
+            _getVailData = getVailData;
+            _knowledgeWordRepository = knowledgeWordRepository;
+            _sysDicDataCacheManager = sysDicDataCacheManager;
+            _identityAppService = identityAppService;
+            _httpClientFactory = httpClientFactory;
+        }
+
+        #endregion
+
+        #region 获取Token
+
+        [AllowAnonymous]
+        [HttpPost("gettoken")]
+        public async Task<string> LoginWithoutCrypt([FromBody] LoginDto dto)
+        {
+            return await _identityAppService.LoginAsync(dto, HttpContext.RequestAborted);
+        }
+
+        #endregion
+
+        #region 受理统计
+
+        /// <summary>getacceptancetypestatisticsbymonth
+        /// 受理统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getacceptancetypestatisticsbymonth")]
+        public async Task<List<OrderFormCount>> GetAcceptanceTypeStatisticsByMonth([FromBody] OrderFormByMonthDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.StartTime))
+                dto.StartTime = DateTime.Now.ToString("yyyy-MM-dd 00:00:00");
+
+            if (string.IsNullOrEmpty(dto.EndTime))
+                dto.EndTime = DateTime.Now.ToString("yyyy-MM-dd 23:59:59");
+
+            //查询待
+            var listType = await _orderRepository.Queryable()
+                .Where(p => p.CreationTime >= Convert.ToDateTime(dto.StartTime) && p.CreationTime <= Convert.ToDateTime(dto.EndTime) && p.Status > EOrderStatus.WaitForAccept)
+                .Select(it => new
+                {
+                    it.AcceptType,
+                    it.AcceptTypeCode
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.AcceptType, it.AcceptTypeCode })//对新表进行分组
+                 .Select(it => new
+                 {
+                     name = it.AcceptType,
+                     value = SqlFunc.AggregateCount(it.AcceptTypeCode)
+                 })
+                 .ToListAsync();
+            var formAcceptanceCount = _mapper.Map<List<OrderFormCount>>(listType);
+
+            if (_appOptions.Value.IsZiGong)
+            {
+                List<OrderFormCount> orderForms = new List<OrderFormCount>();
+                List<string> fromName = ["咨询", "投诉", "求助", "建议", "举报", "表扬", "申报"];
+                foreach (var form in fromName)
+                {
+                    var data = formAcceptanceCount.FirstOrDefault(p => p.name == form);
+                    orderForms.Add(new OrderFormCount()
+                    {
+                        name = form,
+                        value = data == null ? 0 : data.value
+                    });
+                }
+                return orderForms;
+            }
+
+
+            return formAcceptanceCount;
+        }
+
+        #endregion
+
+        #region 办理统计
+
+        /// <summary>
+        /// 办理统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getacceptancetypestatisticsbymonthend")]
+        public async Task<List<OrderFormCount>> GetAcceptanceTypeStatisticsByMonthEnd([FromBody] OrderFormByMonthDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.StartTime))
+                dto.StartTime = DateTime.Now.ToString("yyyy-MM-dd 00:00:00");
+
+            if (string.IsNullOrEmpty(dto.EndTime))
+                dto.EndTime = DateTime.Now.ToString("yyyy-MM-dd 23:59:59");
+
+            //数据查询
+            var listFileType = await _orderRepository.Queryable()
+                .Where(p => p.FiledTime >= Convert.ToDateTime(dto.StartTime) && p.FiledTime <= Convert.ToDateTime(dto.EndTime) && p.Status >= EOrderStatus.Visited)
+                .Select(it => new
+                {
+                    it.AcceptType,
+                    it.AcceptTypeCode
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.AcceptType, it.AcceptTypeCode })//对新表进行分组
+                 .Select(it => new
+                 {
+                     name = it.AcceptType,
+                     value = SqlFunc.AggregateCount(it.AcceptTypeCode)
+                 })
+                 .ToListAsync();
+
+            var formFileCount = _mapper.Map<List<OrderFormCount>>(listFileType);
+            if (_appOptions.Value.IsZiGong)
+            {
+                List<OrderFormCount> orderForms = new List<OrderFormCount>();
+                List<string> fromName = ["咨询", "投诉", "求助", "建议", "举报", "表扬", "申报"];
+                foreach (var form in fromName)
+                {
+                    var data = formFileCount.FirstOrDefault(p => p.name == form);
+                    orderForms.Add(new OrderFormCount()
+                    {
+                        name = form,
+                        value = data == null ? 0 : data.value
+                    });
+                }
+                return orderForms;
+            }
+            return formFileCount;
+        }
+
+        #endregion
+
+        #region 公告通知、工作简报
+
+        /// <summary>
+        /// 公告通知、工作简报
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getarticlelistbynum")]
+        public async Task<IReadOnlyList<DataListTopDto>> GetArticleListByNum([FromBody] ArticleIdByNumDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.PushRanges))
+                dto.PushRanges = "2";
+
+            var items = await _bulletinRepository.Queryable()
+                .Where(p => p.LoseEfficacyTime >= DateTime.Now)
+                .Where(p => p.IsArrive == true)
+                .Where(p => SqlFunc.JsonListObjectAny(p.PushRanges, "Key", dto.PushRanges))
+                //  .Where(p => p.BulletinTypeId == dto.BulletinTypeId)
+                .WhereIF(!string.IsNullOrEmpty(dto.BulletinTypeId), p => p.BulletinTypeId == dto.BulletinTypeId)
+                .WhereIF(!string.IsNullOrEmpty(dto.CheckChar), p => p.Content.Contains(dto.CheckChar))
+                .WhereIF(!string.IsNullOrEmpty(dto.BulletinDisplayLocation), p => SqlFunc.JsonListObjectAny(p.DisplayLocation, "Key", dto.BulletinDisplayLocation))
+                .OrderByDescending(p => p.CreationTime)
+                .Select(it => new
+                {
+                    DataID = it.Id,
+                    it.Title,
+                    CreateDate = it.CreationTime,
+                    it.Content,
+                    it.BulletinTypeName
+                })
+                .Take(dto.Num)
+                .ToListAsync();
+
+            return _mapper.Map<IReadOnlyList<DataListTopDto>>(items);
+        }
+
+        /// <summary>
+        /// 公告通知、工作简报详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getarticledetails")]
+        public async Task<List<ArticleDetailsDto>> GetArticleDetails([FromBody] ArticleIdDto dto)
+        {
+            var data = await _bulletinRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
+            ArticleDetailsDto detailsDto = null;
+            if (data != null)
+            {
+                data.ReadedNum = data.ReadedNum++;
+                await _bulletinRepository.UpdateAsync(data, HttpContext.RequestAborted);
+
+                detailsDto = new()
+                {
+                    NoticeID = data.Id,
+                    NoticeTypeName = data.BulletinTypeName,
+                    NoticeTitle = data.Title,
+                    NoticeBMName = data.SourceOrgName,
+                    NoticeCreateDate = data.CreationTime,
+                    NoticeRCount = data.ReadedNum,
+                    WNED_VideoUrl = "",
+                    NoticeContent = data.Content
+                };
+
+                if (data != null && !string.IsNullOrEmpty(data.Content))
+                    data.Content = _bulletinApplication.GetSiteUrls(data.Content);
+            }
+            else
+                detailsDto = new();
+            List<ArticleDetailsDto> dataDto = new() { detailsDto };
+            return dataDto;
+        }
+
+        #endregion
+
+        #region 信件选登
+
+        /// <summary>
+        /// 查询工单发布后公开的数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("get_order_list_publish_all")]
+        public async Task<OrderListReturnDto> GetOrderByListAllOpen([FromBody] QueryOrderListDto dto)
+        {
+            var queryNew = _orderPublishRepository.Queryable()
+                .LeftJoin<Order>((op, p) => p.Id == op.OrderId)
+                .Where((op, p) => op.PublishState == true)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowCode), (op, p) => p.No == dto.FlowCode)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowName), (op, p) => op.ArrangeTitle.Contains(dto.FlowName))
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSType), (op, p) => p.AcceptTypeCode == dto.FlowSType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowRType), (op, p) => p.HotspotId == dto.FlowRType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSDate), (op, p) => p.StartTime >= DateTime.Parse(DateTime.Parse(dto.FlowSDate).ToString("yyyy-MM-dd 00:00:00")))//dto.FlowSDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowEDate), (op, p) => p.StartTime <= DateTime.Parse(DateTime.Parse(dto.FlowEDate).ToString("yyyy-MM-dd 00:00:00")))// dto.FlowEDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowFrom), (op, p) => p.FromName.Contains(dto.FlowFrom))
+                .WhereIF(dto.IdentityType.HasValue, (op, p) => p.IdentityType == dto.IdentityType)
+                .OrderByDescending((op, p) => p.CreationTime)
+               .Select((op, p) => new OrderListDto
+               {
+                   FlowID = p.Id,
+                   FlowCode = p.No,
+                   FlowPwd = p.Password,
+                   FlowTitle = op.ArrangeTitle,
+                   FlowFromName = p.SourceChannel,
+                   FlowPurTypeName = p.AcceptType,
+                   ConTypeName = p.HotspotName,
+                   FlowAddDate = p.CreationTime,
+                   PubDate = op.CreationTime,
+                   RSFlagName = p.Status >= EOrderStatus.Filed ? "办理完成" : "办理中"
+               });
+
+            var queryold = _oldPublicDataRepository.Queryable()
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowCode), p => p.OrderNo == dto.FlowCode)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowName), p => p.Title.Contains(dto.FlowName))
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowSType), p => p.AcceptTypeCode == dto.FlowSType)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowRType), p => p.HotspotId == dto.FlowRType)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowSDate), p => p.AcceptTime >= DateTime.Parse(DateTime.Parse(dto.FlowSDate).ToString("yyyy-MM-dd 00:00:00")))//dto.FlowSDate
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowEDate), p => p.AcceptTime <= DateTime.Parse(DateTime.Parse(dto.FlowEDate).ToString("yyyy-MM-dd 00:00:00")))// dto.FlowEDate
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowFrom), p => p.FromName.Contains(dto.FlowFrom))
+                      .WhereIF(dto.IdentityType.HasValue, p => p.IdentityType == dto.IdentityType)
+                      .OrderByDescending(p => p.PubDate)
+                        .Select(p => new OrderListDto
+                        {
+                            FlowID = p.OrderId,
+                            FlowCode = p.OrderNo,
+                            FlowPwd = p.OrderPwd,
+                            FlowTitle = p.Title,
+                            FlowFromName = p.SourceChannelCode,
+                            FlowPurTypeName = p.AcceptTypeCode,
+                            ConTypeName = p.HotspotName,
+                            FlowAddDate = p.CreationTime,
+                            PubDate = p.CreationTime,
+                            RSFlagName = p.State == "1" ? "办理完成" : "办理中"
+                        });
+
+            var (total, items) = await _orderRepository.UnionAll(queryNew, queryold)
+                .OrderByDescending(p => p.PubDate)
+                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+            //计算总页数
+            int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+            OrderListReturnDto returnDto = new()
+            {
+                Total = total,
+                PageNum = dto.PageIndex,
+                PageCount = nPageCount,
+                Data = _mapper.Map<IReadOnlyList<OrderListDto>>(items)
+            };
+
+            return returnDto;
+        }
+
+        /// <summary>
+        /// 办件摘编详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getorderdetailbyid")]
+        public async Task<OrderDetail> GetOrderDetailById([FromBody] ArticleIdDto dto)
+        {
+            var data = await _orderRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
+
+            var orderDetail = _mapper.Map<OrderDetail>(data);
+            if (data != null)
+            {
+                //如果是省工单或者是省政民互动的工单,市民不能进行评价
+                if (data.IsProvince == true || data.SourceChannelCode == "ZMHD")
+                    orderDetail.IsProvinceOrder = "1";
+                else
+                    orderDetail.IsProvinceOrder = "0";
+
+                //获取发布的数据
+                var orderPublish = await _orderPublishRepository.GetAsync(p => p.OrderId == data.Id, HttpContext.RequestAborted);
+                if (orderPublish != null)
+                {
+                    orderDetail.PubFlag = "1";
+                    orderDetail.FlowTitle = orderPublish.ArrangeTitle;
+                    orderDetail.FlowContent = orderPublish.ArrangeContent;
+                    orderDetail.FlowResult = orderPublish.ArrangeOpinion;
+                    if (orderPublish.PublishState)
+                        orderDetail.FlowPubFlagName = "公开";
+                }
+
+                //能否进行评价
+                var orderVisit = await _orderVisitRepository.GetAsync(p => p.OrderId == data.Id && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+                if (orderVisit == null)
+                    orderDetail.VisitType = "0";
+                else
+                {
+                    orderDetail.VisitType = orderVisit.VisitState switch
+                    {
+                        EVisitState.WaitForVisit => "1",
+                        EVisitState.Visited => "2",
+                        EVisitState.Visiting or EVisitState.NoSatisfiedWaitForVisit or EVisitState.None => "0",
+                        _ => "0",
+                    };
+                }
+
+            }
+            //List<OrderDetail> dataDto = new() { orderDetail };
+            return orderDetail;
+        }
+
+        #endregion
+
+        #region 知识库
+
+        #region 知识库分类
+
+        /// <summary>
+        /// 知识库分类
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("getknowledgetype")]
+        public async Task<object> GetKnowledgeType()
+        {
+            //查询知识分类
+            var item = await _knowledgeTypeRepository.Queryable()
+                   .Where(p => p.ParentId == null || p.ParentId == "")
+                   .Select(p => new
+                   {
+                       SDICT_ID = p.Id,
+                       SDICT_Name = p.Name
+                   })
+                   .ToListAsync();
+
+            //查询知识标签
+            var list = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.KnowledgeBaseTags).Select(p => new
+            {
+                SDICT_Name = p.DicDataName,
+                SDICT_ID = p.DicDataName
+            }).ToList();
+
+            var rsp = new
+            {
+                KnowledgeType = item,
+                KnowledgeBaseTags = list,
+            };
+            return rsp;
+        }
+
+        #endregion
+
+        #region 知识库查询
+
+        /// <summary>
+        /// 知识库查询
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getknowledgelist")]
+        public async Task<PagedDto<KnowledgeInfoThirdDto>> GetKnowledgeList([FromBody] QueryKnowledgeList dto)
+        {
+            var typeSpliceName = string.Empty;
+            if (!string.IsNullOrEmpty(dto.KnowledgeTypeId))
+            {
+                var type = await _knowledgeTypeRepository.GetAsync(x => x.Name == dto.KnowledgeTypeId);
+                typeSpliceName = type?.SpliceName;
+            }
+
+            var typeSpliceNameTags = string.Empty;
+            if (!string.IsNullOrEmpty(dto.KnowledgeBaseTags))
+            {
+                var type = await _knowledgeWordRepository.GetAsync(x => x.Tag == dto.KnowledgeBaseTags && x.IsEnable == 1);
+                typeSpliceNameTags = type?.Id;
+            }
+
+            var (total, items) = await _knowledgeRepository.Queryable()
+                .Where(p => p.IsPublic == true && p.Status == EKnowledgeStatus.OnShelf)
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title.Contains(dto.Title))
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceNameTags), p => SqlFunc.JsonArrayAny(p.Keywords, typeSpliceNameTags) == true)
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceName)))
+                .OrderByDescending(p => p.CreationTime)
+                .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+            var data = new PagedDto<KnowledgeInfoThirdDto>(total, _mapper.Map<IReadOnlyList<KnowledgeInfoThirdDto>>(items));
+            return data;
+        }
+
+        #endregion
+
+        #region 知识库详情
+
+        /// <summary>
+        /// 知识库详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getknowledgeinfo")]
+        public async Task<KnowledgeInfoThirdDto> GetKnowledgeInfo([FromBody] QueryKnowledgeInfo dto)
+        {
+            var data = await _knowledgeRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
+            KnowledgeInfoThirdDto detailsDto = null;
+            if (data != null)
+            {
+                detailsDto = _mapper.Map<KnowledgeInfoThirdDto>(data);
+
+                if (detailsDto != null && !string.IsNullOrEmpty(detailsDto.Content))
+                    data.Content = _bulletinApplication.GetSiteUrls(data.Content);
+            }
+            else
+            {
+                detailsDto = new();
+            }
+            return detailsDto;
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 工单写信
+
+        /// <summary>
+        /// 工单写信
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("orderacceptance")]
+        public async Task<OpenResponse> OrderAcceptance([FromBody] WebFlowAcceptDto dto)
+        {
+            //电话号码去空格
+            if (!string.IsNullOrEmpty(dto.Mobile))
+                dto.Mobile = dto.Mobile.Trim();
+
+            string strResult = dto.ValidateObject();
+            if (!string.IsNullOrEmpty(strResult))
+            {
+                return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Failed("数据验证不通过!"));
+            }
+
+            var data = _mapper.Map<Hotline.Share.Dtos.Order.AddOrderDto>(dto);
+            data.Source = ESource.WebPortal;
+            data.SourceChannel = "因特网";
+            data.SourceChannelCode = "YTW";
+
+            //switch (dto.FromID)
+            //{
+            //    case "2":
+            //        data.SourceChannel = "APP";
+            //        data.SourceChannelCode = "AP";
+            //        data.Source = ESource.APP;
+            //        break;
+            //    case "3":
+            //        data.SourceChannel = "微信小程序";
+            //        data.SourceChannelCode = "XCX";
+            //        data.Source = ESource.WeChat;
+            //        break;
+            //    case "9"://宜宾人社专用
+            //        data.SourceChannel = "人社APP";
+            //        data.SourceChannelCode = "RSAPP";
+            //        data.Source = ESource.YBHumanSocietyAPP;
+            //        break;
+            //    default:
+            //        break;
+            //}
+            if (!string.IsNullOrEmpty(data.LicenceNo))
+            {
+                data.LicenceTypeCode = "10";
+                data.LicenceType = "中华人民共和国居民身份证";
+            }
+            data.ExternalId = Guid.NewGuid().ToString();
+
+            data.IdentityType = dto.IdentityType;
+            data.Transpond = false;
+            data.IsEnforcementOrder = false;
+
+
+            var result = await _orderApplication.ReceiveOrderFromExternalAsync(data, HttpContext.RequestAborted);
+
+            OrderAcceptanceReturnDto returnDto = new();
+            if (result != null)
+            {
+                returnDto.PWD = result.Password;
+                returnDto.Code = result.No;
+                returnDto.State = "1";
+
+                dto.Pwd = result.Password;
+                dto.Code = result.No;
+                dto.OrderId = result.Id;
+                var dtoData = _mapper.Map<WebFlowAccept>(dto);
+                await _webFlowAcceptRepository.AddAsync(dtoData, HttpContext.RequestAborted);
+
+            }
+            else
+                returnDto.State = "0";
+
+
+            return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Success(returnDto));
+        }
+
+        #endregion
+
+        #region 附件上传
+
+        /// <summary>
+        /// 附件上传
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("uploadfiles")]
+        public async Task<GetResultDto> uploadfiles(IFormFile fileData)
+        {
+            if (fileData == null || fileData.Length == 0)
+            {
+                return new GetResultDto();
+            }
+
+            Stream stream = fileData.OpenReadStream();
+            byte[] fileContentBytes = StreamToBytes(stream);
+            var client = _httpClientFactory.CreateClient("fileData");
+
+            using (var content = new MultipartFormDataContent())
+            {
+                var fileContent = new ByteArrayContent(fileContentBytes);
+                fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
+                {
+                    Name = fileData.Name,
+                    FileName = fileData.FileName
+                };
+                content.Add(fileContent, "fileData", fileData.FileName);
+
+                var response = await client.PostAsync($"{_appOptions.Value.FileUpload.Url.TrimEnd('/')}/file/upload?source=HotlineWeb", content);
+                if (response.IsSuccessStatusCode)
+                {
+                    var result = await response.Content.ReadFromJsonAsync<GetFileDto>();
+                    return new GetResultDto()
+                    {
+                        id = result.result.id,
+                        fileName = result.result.fileName,
+                        path = result.result.path,
+                    };
+                }
+                else
+                {
+                    return new GetResultDto();
+                }
+            }
+        }
+
+        #endregion
+
+        #region 工单查询
+
+        /// <summary>
+        /// 根据编号和密码查询信件ID
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getorderdetailbyno")]
+        public async Task<OrderDetail> GetOrderDetailByNo([FromBody] GetOrderCodePwd dto)
+        {
+            var data = await _orderRepository.GetAsync(p => p.No == dto.OrderNo && p.Password == dto.Pwd, HttpContext.RequestAborted);
+            if (data != null)
+            {
+                var orderDetail = _mapper.Map<OrderDetail>(data);
+                if (data != null)
+                {
+                    //如果是省工单或者是省政民互动的工单,市民不能进行评价
+                    if (data.IsProvince == true || data.SourceChannelCode == "ZMHD")
+                        orderDetail.IsProvinceOrder = "1";
+                    else
+                        orderDetail.IsProvinceOrder = "0";
+
+                    //获取发布的数据
+                    var orderPublish = await _orderPublishRepository.GetAsync(p => p.OrderId == data.Id, HttpContext.RequestAborted);
+                    if (orderPublish != null)
+                    {
+                        orderDetail.PubFlag = "1";
+                        orderDetail.FlowTitle = orderPublish.ArrangeTitle;
+                        orderDetail.FlowContent = orderPublish.ArrangeContent;
+                        orderDetail.FlowResult = orderPublish.ArrangeOpinion;
+                        if (orderPublish.PublishState)
+                            orderDetail.FlowPubFlagName = "公开";
+                    }
+
+                    //能否进行评价
+                    var orderVisit = await _orderVisitRepository.GetAsync(p => p.OrderId == data.Id && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+                    if (orderVisit == null)
+                        orderDetail.VisitType = "0";
+                    else
+                    {
+                        orderDetail.VisitType = orderVisit.VisitState switch
+                        {
+                            EVisitState.WaitForVisit => "1",
+                            EVisitState.Visited => "2",
+                            EVisitState.Visiting or EVisitState.NoSatisfiedWaitForVisit or EVisitState.None => "0",
+                            _ => "0",
+                        };
+                    }
+
+                }
+                List<OrderDetail> dataDto = new() { orderDetail };
+                return orderDetail;
+            }
+            return new OrderDetail();
+        }
+
+
+        #endregion
+
+        #region 私有方法
+
+        /// <summary>
+        /// 将文件转化为文件流
+        /// </summary>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        private byte[] StreamToBytes(Stream stream)
+        {
+            byte[] bytes = new byte[stream.Length];
+            stream.Read(bytes, 0, bytes.Length);
+            // 设置当前流的位置为流的开始    
+            stream.Seek(0, SeekOrigin.Begin);
+            return bytes;
+        }
+
+        #endregion
+    }
+}

+ 3 - 1
src/Hotline.Api/Controllers/HomeController.cs

@@ -193,7 +193,9 @@ public class HomeController : BaseController
             NationalPlatformWordLimit = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NationalPlatformWordLimit).SettingValue[0]),
             HandleOpinionWordLimit= int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.HandleOpinionWordLimit).SettingValue[0]),
             CallInOpenType = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.CallInOpenType).SettingValue[0]),
-            Snapshot = _systemSettingCacheManager.Snapshot
+            Snapshot = _systemSettingCacheManager.Snapshot,
+            IsTelRest = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsTelRest).SettingValue[0]),
+            TelRestNum = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.TelRestNum).SettingValue[0])
         };
         return rsp;
     }

+ 1 - 1
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -1,6 +1,6 @@
 using DotNetCore.CAP;
 using Hotline.CallCenter.Tels.CallTelDomain;
-using ExtendedNumerics.Exceptions;
+//using ExtendedNumerics.Exceptions;
 using Hotline.Ai.Quality;
 using Hotline.Application.CallCenter.Calls;
 using Hotline.Application.Systems;

文件差異過大導致無法顯示
+ 316 - 386
src/Hotline.Api/Controllers/OrderController.cs


+ 6 - 30
src/Hotline.Api/Controllers/TestController.cs

@@ -1,7 +1,5 @@
-using DocumentFormat.OpenXml.Drawing.Charts;
-using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
-using DotNetCore.CAP;
-using Hotline.Ai.Visit;
+using DotNetCore.CAP;
+using Hotline.Ai.Quality;
 using Hotline.Application.CallCenter;
 using Hotline.Application.ExportExcel;
 using Hotline.Application.FlowEngine;
@@ -9,7 +7,6 @@ using Hotline.Application.JudicialManagement;
 using Hotline.Application.Orders;
 using Hotline.Application.Quality;
 using Hotline.Application.StatisticalReport;
-using Hotline.Authentications;
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
@@ -18,9 +15,6 @@ using Hotline.CallCenter.Ivrs;
 using Hotline.CallCenter.Tels;
 using Hotline.Configurations;
 using Hotline.ContingencyManagement;
-using Hotline.FlowEngine;
-using Hotline.FlowEngine.Definitions;
-using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Import;
@@ -28,62 +22,43 @@ using Hotline.JudicialManagement;
 using Hotline.Orders;
 using Hotline.Realtimes;
 using Hotline.Repository.SqlSugar;
-using Hotline.Repository.SqlSugar.CallCenter;
 using Hotline.Repository.SqlSugar.Ts;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
-using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
+using Hotline.Share.Dtos.Home;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Realtime;
+using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.JudicialManagement;
 using Hotline.Share.Enums.Order;
-using Hotline.Share.Enums.Quality;
 using Hotline.Share.Mq;
 using Hotline.Users;
 using Mapster;
 using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Builder.Extensions;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using MiniExcelLibs;
-using Newtonsoft.Json;
-using NPOI.POIFS.Crypt.Dsig;
+using NETCore.Encrypt;
 using SqlSugar;
-using StackExchange.Redis;
-using System.Threading;
 using XC.RSAUtil;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
-using XF.Domain.Exceptions;
 using XF.Domain.Filters;
 using XF.Domain.Locks;
 using XF.Domain.Queues;
 using XF.Domain.Repository;
-using static System.Runtime.InteropServices.JavaScript.JSType;
 using Order = Hotline.Orders.Order;
-using Hotline.Share.Dtos.Settings;
 using OrderDto = Hotline.Share.Dtos.Order.OrderDto;
-using Hotline.Share.Dtos.Home;
-using Google.Protobuf.WellKnownTypes;
-using Microsoft.AspNetCore.DataProtection;
-using Hotline.Share.Tools;
-using NETCore.Encrypt;
-using Hotline.Ai.Quality;
-using Hotline.EventBus;
-using Hotline.JudicialManagement.Notifies;
-using Hotline.Snapshot.Notifications;
-using XF.Domain.Entities;
-using Hotline.Repository.SqlSugar.Orders;
 
 namespace Hotline.Api.Controllers;
 
@@ -281,6 +256,7 @@ ICallApplication callApplication,
     [AllowAnonymous]
     public async Task<ThirdPhoneOutDto> GetPhoneNumberTest()
     {
+
         var inDto = new ThirdTokenDto
         {
             AppId = _systemSettingCacheManager.WxOpenAppId,

+ 1 - 0
src/Hotline.Api/Controllers/UserController.cs

@@ -161,6 +161,7 @@ public class UserController : BaseController
     public async Task<PagedDto<UserDto>> QueryPaged([FromQuery] UserPagedDto dto)
     {
         var query =  _userApplication.QueryPaged(dto);
+        Console.WriteLine(query.ToSqlString());
         var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
 
         //var (total, items) = await _userRepository.Queryable(includeDeleted: true)

+ 3 - 3
src/Hotline.Api/Controllers/WebPortalController.cs

@@ -1674,7 +1674,7 @@ namespace Hotline.Api.Controllers
                 var data = await _knowledgeRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
                 if (data != null)
                 {
-                    data.PageView = data.PageView++;
+                    data.PageView = data.PageView + 1;
                     await _knowledgeRepository.UpdateAsync(data, HttpContext.RequestAborted);
                     detailsDto = new()
                     {
@@ -1698,7 +1698,7 @@ namespace Hotline.Api.Controllers
                 var data = await _bulletinRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
                 if (data != null)
                 {
-                    data.ReadedNum = data.ReadedNum++;
+                    data.ReadedNum = data.ReadedNum + 1;
                     await _bulletinRepository.UpdateAsync(data, HttpContext.RequestAborted);
                     detailsDto = new()
                     {
@@ -1977,7 +1977,7 @@ namespace Hotline.Api.Controllers
                 {
                     orderDetail.VisitType = orderVisit.VisitState switch
                     {
-                        EVisitState.WaitForVisit => "1",
+                        EVisitState.WaitForVisit or EVisitState.SMSVisiting or EVisitState.SMSUnsatisfied => "1",
                         EVisitState.Visited => "2",
                         EVisitState.Visiting or EVisitState.NoSatisfiedWaitForVisit or EVisitState.None => "0",
                         _ => "0",

+ 1 - 1
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -443,7 +443,7 @@ public class WorkflowController : BaseController
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withTracesTree: true,
             cancellationToken: HttpContext.RequestAborted);
-        workflow.Traces = workflow.Traces.Where(d => d.TraceStyle == ETraceStyle.Flow).ToList();
+        workflow.Traces = workflow.Traces.Where(d => d.TraceStyle == ETraceStyle.Flow).Where(d => d.HandleMode != EHandleMode.PreviousNoDisplay).ToList();
 
         var workflowDto = _mapper.Map<WorkflowDto>(workflow);
         if (workflowDto.Traces.Any())

二進制
src/Hotline.Api/Documents/luzhou/丰窝12345平台工单接入接口(V1.1-20250225) .docx


+ 11 - 8
src/Hotline.Api/Realtimes/RealtimeMethods.cs

@@ -14,7 +14,7 @@
         /// <summary>
         /// 电话挂断通知
         /// </summary>
-        public static string Bye= "Bye";
+        public static string Bye = "Bye";
         /// <summary>
         /// 小休审核通过通知
         /// </summary>
@@ -41,6 +41,10 @@
         /// </summary>
         public static string CircularRecord = "CircularRecord";
 
+        /// <summary>
+        /// 工单补充消息通知
+        /// </summary>
+        public static string OrderComplementRecord = "OrderComplementRecord";
 
         #endregion
 
@@ -55,13 +59,13 @@
         public static string BsDataShowArea7 = "BsDataShowArea7";
         public static string BsDataShowArea8 = "BsDataShowArea8";
 
-		#endregion
+        #endregion
 
-		#region 大屏-坐席数据
-		public static string BsSeatStateDataShowArea1 = "BsSeatStateDataShowArea1";
-		public static string BsSeatStateDataShowArea2 = "BsSeatStateDataShowArea2";
-		public static string BsSeatStateDataShowArea3 = "BsSeatStateDataShowArea3";
-		public static string BsSeatStateDataShowArea4 = "BsSeatStateDataShowArea4";
+        #region 大屏-坐席数据
+        public static string BsSeatStateDataShowArea1 = "BsSeatStateDataShowArea1";
+        public static string BsSeatStateDataShowArea2 = "BsSeatStateDataShowArea2";
+        public static string BsSeatStateDataShowArea3 = "BsSeatStateDataShowArea3";
+        public static string BsSeatStateDataShowArea4 = "BsSeatStateDataShowArea4";
         #endregion
 
         #region 大屏数据
@@ -86,4 +90,3 @@
         #endregion
     }
 }
-    

+ 26 - 25
src/Hotline.Api/Realtimes/RealtimeService.cs

@@ -5,7 +5,6 @@ using Hotline.Realtimes;
 using Hotline.Share.Dtos.Realtime;
 using Hotline.Users;
 using Microsoft.AspNetCore.SignalR;
-using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
@@ -130,16 +129,16 @@ public class RealtimeService : IRealtimeService, IScopeDependency
     /// <param name="id"></param>
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
-    public async Task CircularRecoordAsync(string id, CancellationToken cancellationToken)
+    public async Task CircularRecoordAsync(string id,string title,string sendContent, CancellationToken cancellationToken)
     {
         //获取所有需要推送的
         var record = await _circularRecordRepository.Queryable().Where(x => x.Id == id).FirstAsync();
 
         if (record.CircularType == Share.Enums.Article.ECircularType.Person)
         {
-            //个人
+            //个人数量
             await SendToUserAsync(record.UserId, RealtimeMethods.CircularRecord,
-                new CircularRecoordDto() { CircularType = Share.Enums.Article.ECircularType.Person, Count = record.RecordCount },
+                new CircularRecoordDto() { CircularType = Share.Enums.Article.ECircularType.Person, Count = record.RecordCount,SendContent = sendContent,Title= title },
                 cancellationToken);
         }
         else
@@ -150,10 +149,12 @@ public class RealtimeService : IRealtimeService, IScopeDependency
             {
                 try
                 {
+                    //数量
                     await SendToUserAsync(user.Id, RealtimeMethods.CircularRecord,
                         new CircularRecoordDto()
-                            { CircularType = Share.Enums.Article.ECircularType.Org, Count = record.RecordCount },
+                        { CircularType = Share.Enums.Article.ECircularType.Org, Count = record.RecordCount,SendContent = sendContent,Title = title },
                         cancellationToken);
+
                 }
                 catch
                 {
@@ -166,37 +167,37 @@ public class RealtimeService : IRealtimeService, IScopeDependency
 
     #region 大屏.数据展示
 
-    public Task BsDataShowChanged1Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged1Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea1, obj, cancellationToken);
-    public Task BsDataShowChanged2Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged2Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea2, obj, cancellationToken);
-    public Task BsDataShowChanged3Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged3Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea3, obj, cancellationToken);
-    public Task BsDataShowChanged4Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged4Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea4, obj, cancellationToken);
-    public Task BsDataShowChanged5Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged5Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea5, obj, cancellationToken);
-    public Task BsDataShowChanged6Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged6Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea6, obj, cancellationToken);
-    public Task BsDataShowChanged7Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged7Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea7, obj, cancellationToken);
-    public Task BsDataShowChanged8Async(object obj, CancellationToken cancellationToken) => 
+    public Task BsDataShowChanged8Async(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigScreenDataShow, RealtimeMethods.BsDataShowArea8, obj, cancellationToken);
 
-	#endregion
+    #endregion
 
-	#region 大屏坐席数据
-	public Task BsSeatStateDataShowChangedAsync1(object obj, CancellationToken cancellationToken) =>
-		SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea1, obj, cancellationToken);
+    #region 大屏坐席数据
+    public Task BsSeatStateDataShowChangedAsync1(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea1, obj, cancellationToken);
 
-	public Task BsSeatStateDataShowChangedAsync2(object obj, CancellationToken cancellationToken) =>
-		SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea2, obj, cancellationToken);
+    public Task BsSeatStateDataShowChangedAsync2(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea2, obj, cancellationToken);
 
-	public Task BsSeatStateDataShowChangedAsync3(object obj, CancellationToken cancellationToken) =>
-		SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea3, obj, cancellationToken);
+    public Task BsSeatStateDataShowChangedAsync3(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea3, obj, cancellationToken);
 
-	public Task BsSeatStateDataShowChangedAsync4(object obj, CancellationToken cancellationToken) =>
-		SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea4, obj, cancellationToken);
+    public Task BsSeatStateDataShowChangedAsync4(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigScreenSeatState, RealtimeMethods.BsSeatStateDataShowArea4, obj, cancellationToken);
     #endregion
 
     #region 数据大屏
@@ -217,7 +218,7 @@ public class RealtimeService : IRealtimeService, IScopeDependency
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
     public Task OrderHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
-        SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderHandlingDetail,obj, cancellationToken);
+        SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderHandlingDetail, obj, cancellationToken);
 
     /// <summary>
     /// 推送二次办理中工单概览
@@ -282,6 +283,6 @@ public class RealtimeService : IRealtimeService, IScopeDependency
 
     #endregion
 
-   
+
 
 }

+ 1 - 1
src/Hotline.Api/StartupExtensions.cs

@@ -200,7 +200,7 @@ internal static class StartupExtensions
         services.AddMq(configuration);
 
         //job
-        services.RegisterJob(appConfiguration);
+        //services.RegisterJob(appConfiguration);
 
         services.AddPdfManager();
 

+ 1 - 0
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -137,6 +137,7 @@ public abstract class DefaultCallApplication : ICallApplication
         if (telOperation == null) return;
         telOperation.EndAction();
         await _telOperationRepository.UpdateAsync(telOperation);
+        _logger.LogInformation($"分机动作结束成功: 入参 [{dto.ToJson()}] 受影响 TelOperationId: {telOperation.Id}");
     }
 
     /// <summary>

+ 2 - 2
src/Hotline.Application/Enterprise/EnterpriseApplication.cs

@@ -49,8 +49,8 @@ public class EnterpriseApplication : IEnterpriseApplication, IScopeDependency
             .ToListAsync(cancellationToken);
         return _orderRepository.Queryable()
                 .Where(d => memberIds.Contains(d.CreatorId))
-                .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Title.Contains(dto.No!))
-                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!))
+                .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title.Contains(dto.Title!))
                 .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
                 .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
                 .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席

+ 9 - 0
src/Hotline.Application/Exam/Constants/SourcewareCategoryRouteApi.cs

@@ -0,0 +1,9 @@
+using Exam.Infrastructure.Web.Constants;
+
+namespace Hotline.Application.Exam.Constants
+{
+    public class SourcewareCategoryRouteApi:RouteApi
+    {
+        public const string GetTreeList = "GetTreeList";
+    }
+}

+ 9 - 0
src/Hotline.Application/Exam/Constants/SourcewareRouteApi.cs

@@ -0,0 +1,9 @@
+using Exam.Infrastructure.Web.Constants;
+
+namespace Hotline.Application.Exam.Constants
+{
+    public class SourcewareRouteApi:RouteApi
+    {
+        
+    }
+}

+ 25 - 0
src/Hotline.Application/Exam/Extensions/ActionRequestExtensions.cs

@@ -0,0 +1,25 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Extensions;
+using XF.Domain.Authentications;
+
+namespace Exam.Infrastructure.Data.Extensions
+{
+    public static class ActionRequestExtensions
+    {
+
+        public static void InitRequest(this ActionRequest actionRequest,ISessionContext sessionContext)
+        {
+            if (actionRequest.Id == null || actionRequest.Id.IsNullOrEmpty())
+            {
+                actionRequest.Id = Guid.NewGuid().ToString();
+            }
+
+            actionRequest.UserId = sessionContext.UserId;
+            actionRequest.UserName = sessionContext.UserName;
+            actionRequest.OrgId = sessionContext.OrgId;
+            actionRequest.AreaId = sessionContext.AreaId;
+            actionRequest.OrgName = sessionContext.OrgName;
+            actionRequest.OrgLevel = sessionContext.OrgLevel;
+        }
+    }
+}

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

@@ -0,0 +1,13 @@
+using Exam.ExamManages;
+
+using Exam.Share;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Repository.SqlSugar.Interface;
+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 Hotline.Repository.SqlSugar.Interface;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+
+namespace Exam.Application.Interface.Exam
+{
+    public interface IExamTagService:IQueryService<ExamTagViewResponse,ExamTagDto,ExamTagRequest>,IApiService<ExamTagDto,ExamTag>
+    {
+    }
+}

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

@@ -0,0 +1,13 @@
+using Exam.ExamManages;
+
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}

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

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

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

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

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

@@ -0,0 +1,29 @@
+using Exam.ExamManages;
+
+using Exam.Share;
+using Exam.Share.Dtos.ExamManage;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Repository.SqlSugar.Interface;
+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);
+    }
+}

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

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

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

@@ -0,0 +1,21 @@
+
+
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}
+
+

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

@@ -0,0 +1,21 @@
+
+
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}
+
+

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

@@ -0,0 +1,21 @@
+
+
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}
+
+

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

@@ -0,0 +1,13 @@
+
+using Exam.Questions;
+using Exam.Share.ViewResponses.Question;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}

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

@@ -0,0 +1,21 @@
+
+
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}
+
+

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

@@ -0,0 +1,21 @@
+
+
+using Exam.Questions;
+using Exam.Share;
+using Hotline.Repository.SqlSugar.Interface;
+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.Share.ViewResponses.Sourceware;
+using Hotline.Exams.Sourcewares;
+using Hotline.Repository.SqlSugar.Interface;
+using Hotline.Share.Dtos.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>
+    {
+        public Task<List<SourcewareCategoryViewResponse>> GetTreeAsync(SourcewareCategoryRequest sourcewareCategoryRequest);
+    }
+}
+
+

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

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

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

@@ -0,0 +1,13 @@
+
+using Exam.Share.ViewResponses.TestPaper;
+using Exam.TestPapers;
+using Hotline.Repository.SqlSugar.Interface;
+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>
+    {
+    }
+}

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

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

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

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

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

@@ -0,0 +1,20 @@
+
+using Exam.Share.Dtos.Trains;
+using Exam.Share.ViewResponses.Train;
+using Exam.Trains;
+using Hotline.Repository.SqlSugar.Interface;
+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);
+    }
+}

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

@@ -0,0 +1,13 @@
+
+using Exam.Share.ViewResponses.Train;
+using Exam.Trains;
+using Hotline.Repository.SqlSugar.Interface;
+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/ExamManages/ExamTagQueryExtensions.cs

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

+ 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;
+        }
+
+    }
+}
+
+
+

部分文件因文件數量過多而無法顯示