diff --git a/src/service/.editorconfig b/src/service/.editorconfig
new file mode 100644
index 0000000..e8bd25d
--- /dev/null
+++ b/src/service/.editorconfig
@@ -0,0 +1,175 @@
+[*.cs]
+
+# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+dotnet_diagnostic.CS8618.severity = silent
+csharp_space_around_binary_operators = before_and_after
+csharp_indent_labels = no_change
+csharp_using_directive_placement = outside_namespace:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+csharp_style_var_elsewhere = false:silent
+
+[*.{cs,vb}]
+end_of_line = crlf
+tab_width = 4
+indent_size = 4
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_code_quality_unused_parameters = all:suggestion
+dotnet_style_readonly_field = true:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_allow_multiple_blank_lines_experimental = true:silent
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+[*.cs]
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.接口_should_be_以_i_开始.severity = suggestion
+dotnet_naming_rule.接口_should_be_以_i_开始.symbols = 接口
+dotnet_naming_rule.接口_should_be_以_i_开始.style = 以_i_开始
+
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+# 符号规范
+
+dotnet_naming_symbols.接口.applicable_kinds = interface
+dotnet_naming_symbols.接口.applicable_accessibilities = public, internal, private, protected, protected_internal
+dotnet_naming_symbols.接口.required_modifiers =
+
+dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.类型.applicable_accessibilities = public, internal, private, protected, protected_internal
+dotnet_naming_symbols.类型.required_modifiers =
+
+dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
+dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, internal, private, protected, protected_internal
+dotnet_naming_symbols.非字段成员.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.以_i_开始.required_prefix = I
+dotnet_naming_style.以_i_开始.required_suffix =
+dotnet_naming_style.以_i_开始.word_separator =
+dotnet_naming_style.以_i_开始.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+[*.vb]
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
+dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
+dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
+
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.类型.required_modifiers =
+
+dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
+dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.非字段成员.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.以_i_开始.required_prefix = I
+dotnet_naming_style.以_i_开始.required_suffix =
+dotnet_naming_style.以_i_开始.word_separator =
+dotnet_naming_style.以_i_开始.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
diff --git a/src/service/EI_TestProject/APITest.cs b/src/service/EI_TestProject/APITest.cs
new file mode 100644
index 0000000..bf079ad
--- /dev/null
+++ b/src/service/EI_TestProject/APITest.cs
@@ -0,0 +1,926 @@
+using System.Net;
+using System.Text;
+using System.Text.Json.Nodes;
+using Azure.Core;
+using BeetleX.Clients;
+using BeetleX.Redis.Commands;
+using EI_TestProject;
+using IRaCIS.Application.Contracts;
+using IRaCIS.Core.Application.Contracts;
+using IRaCIS.Core.Application.ViewModel;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Infrastructure;
+using IRaCIS.Core.Infrastructure.Extention;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Org.BouncyCastle.Asn1.Crmf;
+using Org.BouncyCastle.Asn1.Ocsp;
+using RestSharp;
+using Xunit;
+using Xunit.Abstractions;
+
+public class UserApiTests : IDisposable
+{
+ private RestClient _client;
+
+
+
+ public UserApiTests()
+ {
+ // һ RestClient û URL
+ //client = new RestClient("http://localhost:6100");
+
+ _client = RestHelper.InitRestHelper("http://123.56.94.154:8090/api", "Admin", MD5Helper.Md5("WHxckj@2019"));
+
+ }
+
+ #region û û
+ ///
+ /// ϵͳû
+ ///
+ [Fact]
+ public async void Test_AddUser()
+ {
+
+ var url = $"/user/addUser";
+
+ var jsonBody = new JObject
+ {
+ { "UserName", "test_hang" },
+ { "LastName", "zhou" },
+ { "FirstName", "hang" },
+ { "Sex", 1 },
+ { "EMail", "872297557@qq.com" },
+ { "Phone", "" },
+ { "UserTypeId", "40240000-3e2c-0016-b35f-08db1895d627" },
+ { "IsZhiZhun", "" },
+ { "OrganizationName", "" },
+ { "DepartmentName", "" },
+ { "PositionName", "" },
+ { "IsTestUser", true },
+ { "UserTypeEnum", 1 },
+ { "BaseUrl", "http://123.56.94.154:8090/login" },
+ { "RouteUrl", "http://123.56.94.154:8090/email-recompose" },
+ { "RealName", string.Empty }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// û
+ ///
+ [Fact]
+ public async void Test_UpdateUser()
+ {
+ var url = $"/user/updateUser";
+
+ var jsonBody = new JObject
+ {
+ { "CanEditUserType", false },
+ { "Id", "d83f0000-3e2c-0016-2ed8-08db3f051caf" },
+ { "UserName", "zhouhang" },
+ { "Password", "e10adc3949ba59abbe56e057f20f883e" },
+ { "RealName", "zhou / hang" },
+ { "FirstName", "hang" },
+ { "LastName", "zhou" },
+ { "Sex", 1 },
+ { "Status", 1 },
+ { "Phone", "17673237815" },
+ { "EMail", "872297557@qq.com" },
+ { "UserTypeId", "40240000-3e2c-0016-b35f-08db1895d627" },
+ { "UserCode", "U0178" },
+ { "UserType", "PM (Project Manager)" },
+ { "UserTypeShortName", "PM" },
+ { "UserTypeEnum", 1 },
+ { "IsZhiZhun", true },
+ { "OrganizationName", "ExtImaging" },
+ { "DepartmentName", "xx" },
+ { "PositionName", "xx" },
+ { "IsTestUser", false },
+ { "BaseUrl", "http://123.56.94.154:8090/login" },
+ { "RouteUrl", "http://123.56.94.154:8090/email-recompose" }
+ };
+
+ var result = await RestHelper.Put_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ ///
+ ///
+ [Fact]
+ public async void Test_ResetPassword()
+ {
+ var id = Guid.Parse("d05c0000-3e2c-0016-64eb-08db617a483b");
+ var url = $"/user/resetPassword/{id}";
+
+ var result = await RestHelper.GetRequestAsync>(url);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+
+ }
+
+ ///
+ /// ѯϵͳû
+ ///
+ [Fact]
+ public async void Test_GetUserList()
+ {
+ var url = $"/user/getUserList";
+
+ var jsonBody = new JObject
+ {
+ { "RealName", string.Empty }
+ };
+
+ //ɾApi
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+
+
+ ///
+ /// ɾϵͳû
+ ///
+ [Fact]
+ public async void Test_DeleteUser()
+ {
+ var userId = Guid.Parse("98430000-3e2c-0016-225f-08db7612c7ae");
+ var url = $"/user/deleteUser/{userId}";
+
+ //ɾApi
+ var result = await RestHelper.DeleteRequestAsync>(url);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ /// ɫ
+ ///
+ [Fact]
+ public async void Test_GetUserTypeRoleList()
+ {
+ var url = $"/UserTypeRole/getUserTypeRoleList";
+ var jsonBody = new JObject
+ {
+
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.Count >= 0);
+ }
+
+ #endregion
+
+
+ #region
+ ///
+ ///
+ ///
+ [Fact]
+ public async void Test_getSystemAnonymizationList()
+ {
+ var url = $"/SystemAnonymization/getSystemAnonymizationList";
+
+ var jsonBody = new JObject
+ {
+ { "Group", "" },
+ { "Element", "" },
+ { "TagDescription", "" },
+ { "IsAdd", false },
+ { "TagDescriptionCN", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 500 }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+
+
+ ///
+ /// ɾ
+ ///
+ [Fact]
+ public async void Test_DeleteSystemAnonymization()
+ {
+ var id = Guid.Parse("98430000-3e2c-0016-225f-08db7612c7ae");
+ var url = $"/SystemAnonymization/deleteSystemAnonymization/{id}";
+
+ //ɾApi
+ var result = await RestHelper.DeleteRequestAsync>(url);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ ///
+ ///
+ [Fact]
+ public async void Test_UpdateSystemAnonymization()
+ {
+
+ var url = $"/SystemAnonymization/addOrUpdateSystemAnonymization";
+
+ var jsonBody = new JObject
+ {
+ { "CreateUserId", "e2e165a0-44b0-4b16-9a4d-0b9b7a1ce362" },
+ { "UpdateTime", "2023-06-19 14:36:56" },
+ { "UpdateUserId", "e2e165a0-44b0-4b16-9a4d-0b9b7a1ce362" },
+ { "CreateTime", "2023-06-15 10:21:19" },
+ { "Id", "640a0000-3e2c-0016-ae9b-08db6d4734c9" },
+ { "Group", "1" },
+ { "Element", "2" },
+ { "TagDescription", "3" },
+ { "TagDescriptionCN", "4" },
+ { "ReplaceValue", "123" },
+ { "ValueRepresentation", "5" },
+ { "IsAdd", false },
+ { "IsEnable", true },
+ { "IsFixed", true }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ ///
+ ///
+ [Fact]
+ public async void Test_AddSystemAnonymization()
+ {
+
+ var url = $"/SystemAnonymization/addOrUpdateSystemAnonymization";
+
+
+ var jsonBody = new JObject
+ {
+ { "Group", "0010" },
+ { "Element", "0021" },
+ { "TagDescription", "test" },
+ { "TagDescriptionCN", "test" },
+ { "ValueRepresentation", "test" },
+ { "ReplaceValue", "test" },
+ { "IsEnable", false },
+ { "IsAdd", false },
+ { "IsFixed", false }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+ #endregion
+
+
+ #region Ŀ
+
+ /// ѯϵĿб
+ ///
+ [Fact]
+ public async void Test_GetTrialList()
+ {
+ var url = $"/trial/getTrialList";
+
+ var jsonBody = new JObject
+ {
+ { "Code", "" },
+ { "CriterionIds", new JArray() },
+ { "SponsorId", "" },
+ { "ReviewTypeIds", new JArray() },
+ { "CROId", "" },
+ { "Expedited", "" },
+ { "Indication", "" },
+ { "Phase", "" },
+ { "ModalityIds", new JArray() },
+ { "BeginDate", "" },
+ { "EndDate", "" },
+ { "AttendedReviewerType", "" },
+ { "ResearchProgramNo", "" },
+ { "ExperimentName", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "Asc", false },
+ { "SortField", "" }
+ };
+
+ //ɾApi
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+
+
+ ///
+ /// Ŀ
+ ///
+ [Fact]
+ public async void Test_UpdateTrial()
+ {
+ var pmclient = RestHelper.InitRestHelper("http://123.56.94.154:8090/api", "PM01", MD5Helper.Md5("WHxckj@2019"));
+
+ var url = $"/trial/addOrUpdateTrial";
+
+ var jsonBody = new JObject
+ {
+ { "Id", "20430000-3e2c-0016-aa08-08db791c6ae5" },
+ { "TrialCode", "23000067" },
+ { "TrialType", 1 },
+ { "SponsorId", "" },
+ { "CROId", "" },
+ { "ReviewModeId", "" },
+ { "ReviewTypeIds", new JArray() },
+ { "Expedited", 0 },
+ { "ModalityIds", new JArray() },
+ { "Note", "" },
+ { "ExpectedPatients", 0 },
+ { "TimePointsPerPatient", 0 },
+ { "ProjectCycle", "" },
+ { "TotalReviewers", 0 },
+ { "DeclarationTypeId", "742e0000-3e2c-0016-b5ca-08da6222442f" },
+ { "IndicationTypeId", "437a81b3-c5ad-49fd-82a1-8f441c4ba8da" },
+ { "PhaseId", "b1509dde-234a-46ce-cf13-08d9c120c961" },
+ { "AttendedReviewerType", 0 },
+ { "IsLocked", false },
+ { "ResearchProgramNo", "111" },
+ { "ExperimentName", "Ŀ1" },
+ { "MainResearchUnit", "" },
+ { "HeadPI", "" },
+ { "PlanSiteCount", 0 },
+ { "PlanVisitCount", 0 },
+ { "Indication", "ΰ" },
+ { "IndicationOther", null }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody,restClient: pmclient);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// Ŀ
+ ///
+ ///
+ [Fact]
+ public async void Test_AddTrial()
+ {
+ var pmclient = RestHelper.InitRestHelper("http://123.56.94.154:8090/api", "PM01", MD5Helper.Md5("WHxckj@2019"));
+
+ var url = $"/trial/addOrUpdateTrial";
+
+ var jsonBody = new JObject
+ {
+ { "Id", "" },
+ { "TrialType", 1 },
+ { "SponsorId", "" },
+ { "CROId", "" },
+ { "ReviewModeId", "" },
+ { "ReviewTypeIds", new JArray() },
+ { "Expedited", 0 },
+ { "ModalityIds", new JArray("b44915a2-6f08-45f8-bd6c-74abad2298e1", "80480000-3e2c-0016-f090-08da6b92accd") },
+ { "Note", "" },
+ { "ExpectedPatients", 0 },
+ { "TimePointsPerPatient", 0 },
+ { "ProjectCycle", "" },
+ { "TotalReviewers", 0 },
+ { "DeclarationTypeId", "742e0000-3e2c-0016-b5ca-08da6222442f" },
+ { "IndicationTypeId", "437a81b3-c5ad-49fd-82a1-8f441c4ba8da" },
+ { "PhaseId", "b1509dde-234a-46ce-cf13-08d9c120c961" },
+ { "AttendedReviewerType", 0 },
+ { "IsLocked", false },
+ { "ResearchProgramNo", "111" },
+ { "ExperimentName", "test" },
+ { "MainResearchUnit", "" },
+ { "HeadPI", "zz" },
+ { "PlanSiteCount", null },
+ { "PlanVisitCount", null },
+ { "Indication", "ΰ" },
+ { "IndicationOther", null }
+ };
+
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody, restClient: pmclient);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// ɾĿ
+ ///
+ [Fact]
+ public async void Test_DeleteTrial()
+ {
+ var pmclient = RestHelper.InitRestHelper("http://123.56.94.154:8090/api", "PM01", MD5Helper.Md5("WHxckj@2019"));
+
+ var url = $"/TrialConfig/abandonTrial/dc080000-3e2c-0016-fd6b-08db8cb80e5d/true";
+
+ var jsonBody = new JObject
+ { };
+
+
+ var result = await RestHelper.Put_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ #endregion
+
+
+ #region
+
+ ///
+ /// ѯб
+ ///
+ [Fact]
+ public async void Test_GetSubjctList()
+ {
+ var url = $"/subject/getSubjectList";
+
+ var jsonBody = new JObject
+ {
+ { "Code", "" },
+ { "Status", "" },
+ { "SiteId", "" },
+ { "ShortName", "" },
+ { "Sex", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "TrialId", "dc080000-3e2c-0016-64c6-08db8cb8ed81" }
+ };
+
+ //ɾApi
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+ ///
+ /// ӻ
+ ///
+ [Fact]
+ public async void Test_AddSubjet()
+ {
+
+ var url = $"/subject/addOrUpdateSubject";
+ var jsonBody = new JObject
+ {
+ { "Id", "" },
+ { "Code", "01001" },
+ { "ShortName", "" },
+ { "Height", "" },
+ { "Weight", "" },
+ { "Age", "23" },
+ { "Sex", "" },
+ { "BirthDate", "" },
+ { "SiteId", "db83e2f5-1f2e-408f-a45b-08d8e1dcace0" },
+ { "MedicalNo", "" },
+ { "Status", 1 },
+ { "FirstGiveMedicineTime", "" },
+ { "OutEnrollmentTime", "" },
+ { "VisitOverTime", "" },
+ { "Reason", "" },
+ { "StudyCount", "" },
+ { "SignDate", "" },
+ { "IsUrgent", false },
+ { "TrialId", "dc080000-3e2c-0016-64c6-08db8cb8ed81" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// »
+ ///
+ [Fact]
+ public async void Test_UpdateSubject()
+ {
+ var url = $"/subject/addOrUpdateSubject";
+ var jsonBody = new JObject
+ {
+ { "Id", "20430000-3e2c-0016-a444-08db791c6dad" },
+ { "Code", "12" },
+ { "ShortName", "" },
+ { "Height", "" },
+ { "Weight", "" },
+ { "Age", 33 },
+ { "Sex", "" },
+ { "BirthDate", null },
+ { "SiteId", "db83e2f5-1f2e-408f-a45b-08d8e1dcace0" },
+ { "MedicalNo", "" },
+ { "Status", 1 },
+ { "FirstGiveMedicineTime", null },
+ { "OutEnrollmentTime", null },
+ { "VisitOverTime", null },
+ { "Reason", "" },
+ { "StudyCount", "" },
+ { "SignDate", null },
+ { "IsUrgent", false },
+ { "TrialId", "20430000-3e2c-0016-aa08-08db791c6ae5" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// ɾ
+ ///
+
+ [Fact]
+ public async void Test_DeleteSubjet()
+ {
+ var url = $"/subject/deleteSubject/20430000-3e2c-0016-aa08-08db791c6ae5/20430000-3e2c-0016-d10d-08db791c6ef0";
+
+ //ɾApi
+ var result = await RestHelper.DeleteRequestAsync>(url);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ #endregion
+
+
+ #region Ա
+ ///
+ /// ѯԱб
+ ///
+ [Fact]
+ public async void Test_GetTrialUserList()
+ {
+ var url = $"/trialMaintenance/getMaintenanceUserList";
+
+ var jsonBody = new JObject
+ {
+ { "UserRealName", "" },
+ { "UserName", "" },
+ { "UserTypeId", "" },
+ { "OrganizationName", "" },
+ { "IsDeleted", null },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "TrialId", "20430000-3e2c-0016-aa08-08db791c6ae5" }
+ };
+
+ //ɾApi
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+
+ ///
+ /// ѯԱб
+ ///
+ [Fact]
+ public async void Test_GetTrialUserScreeningList()
+ {
+ var url = $"/trialMaintenance/getTrialUserScreeningList";
+
+ var jsonBody = new JObject
+ {
+ { "UserRealName", "" },
+ { "UserName", "" },
+ { "UserTypeId", "" },
+ { "OrganizationName", "" },
+ { "IsDeleted", null },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "TrialId", "20430000-3e2c-0016-aa08-08db791c6ae5" }
+ };
+
+ //ɾApi
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+
+
+ ///
+ /// ӲԱ
+ ///
+ [Fact]
+ public async void Test_AddTrialUser()
+ {
+
+ var url = $"/trialMaintenance/addTrialUsers";
+ var jsonBody = new JArray
+ {
+ new JObject
+ {
+ { "Sex", 1 },
+ { "Phone", "" },
+ { "EMail", "446322065@qq.com" },
+ { "DepartmentName", "" },
+ { "PositionName", "" },
+ { "UserName", "IR02" },
+ { "IsSelect", false },
+ { "UserTypeId", "40240000-3e2c-0016-ad69-08db18965090" },
+ { "UserType", "IR" },
+ { "UserTypeEnum", 13 },
+ { "OrganizationName", "" },
+ { "UserRealName", "IR02 / IR02" },
+ { "UserId", "9c3b0000-3e2c-0016-3606-08db706de699" },
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" }
+ }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>(url, jsonBody);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ /// Ŀû
+ ///
+ [Fact]
+ public async void Test_UpdateTrialUser()
+ {
+ var pmclient = RestHelper.InitRestHelper("http://123.56.94.154:8090/api", "PM01", MD5Helper.Md5("WHxckj@2019"));
+
+ var url = $"/trialMaintenance/updateTrialUser";
+
+ var jsonBody = new JObject
+ {
+ { "id", "dc080000-3e2c-0016-beab-08db8cce772d" },
+ { "trialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" },
+ { "isDeleted", true },
+ { "removeTime", "2023-07-03" },
+ { "joinTime", "" }
+ };
+
+ var result = await RestHelper.Put_JsonBodyRequestAsync>(url, jsonBody,restClient: pmclient);
+
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ #endregion
+
+
+ #region Ƭ
+
+
+ ///
+ /// Ƭб
+ ///
+ [Fact]
+ public async void Test_GetTrialSubjectAssignAndTask()
+ {
+ var url = $"/VisitTask/getSubjectAssignAndTaskStatList";
+
+ var jsonBody = new JObject
+ {
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "Asc", true },
+ { "SortField", "" },
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" },
+ { "SiteId", null },
+ { "SubjectId", null },
+ { "SubjectCode", null },
+ { "DoctorUserId", null },
+ { "IsHaveAssigned", null },
+ { "IsAssignConfirmed", null },
+ { "TrialReadingCriterionId", "e4070000-3e2c-0016-9b99-08db373d9aed" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.CurrentPageData.Count >= 0);
+ }
+
+ ///
+ /// ƬƬ
+ ///
+ [Fact]
+ public async void Test_batchAssignDoctorToSubject()
+ {
+ var url = $"/VisitTask/batchAssignDoctorToSubject";
+
+ var jsonBody = new JObject
+ {
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" },
+ { "TrialReadingCriterionId", "e4070000-3e2c-0016-9b99-08db373d9aed" },
+ { "SubjectIdList", new JArray("7c3f0000-3e2c-0016-1129-08db51c37208") },
+ {
+ "DoctorArmList", new JArray(
+ new JObject
+ {
+ { "ArmEnum", 0 },
+ { "DoctorUserId", "e4070000-3e2c-0016-4e7a-08db37438068" }
+ }
+ )
+ }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+
+ ///
+ /// ȡɷƬб
+ ///
+
+ [Fact]
+ public async void Test_getDoctorSelectList()
+ {
+ var url = $"/TaskAllocationRule/getDoctorSelectList";
+
+ var jsonBody = new JObject
+ {
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" },
+ { "ReadingCategory", 2 },
+ { "TrialReadingCriterionId", "e4070000-3e2c-0016-9b99-08db373d9aed" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Data.Count >= 0);
+ }
+
+
+ ///
+ /// ȡƬ
+ ///
+ [Fact]
+ public async void Test_cancelSubjectAssignedDoctor()
+ {
+ var url = $"/VisitTask/cancelSubjectAssignedDoctor";
+
+ var jsonBody = new JObject
+ {
+ {
+ "CancelList", new JArray(
+ new JObject
+ {
+ { "Id", "dc080000-3e2c-0016-616d-08db8cd1f7ba" },
+ { "SubjectId", "7c3f0000-3e2c-0016-1129-08db51c37208" },
+ { "DoctorUserId", "e4070000-3e2c-0016-4e7a-08db37438068" },
+ { "ArmEnum", 0 },
+ { "IsCancelAssign", true }
+ }
+ )
+ },
+ { "Note", "\nȡƬ(IR01( / ));" },
+ { "TrialReadingCriterionId", "e4070000-3e2c-0016-9b99-08db373d9aed" },
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ ///
+ /// б
+ ///
+ [Fact]
+ public async void Test_getCRCVisitList()
+ {
+ var url = $"/QCList/getCRCVisitList";
+
+ var jsonBody = new JObject
+ {
+ { "SubjectInfo", "" },
+ { "SiteId", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "VisitPlanArray", new JArray() },
+ { "AuditStateArray", new JArray() },
+ { "SubmitState", null },
+ { "ChallengeState", null },
+ { "BeginSubmitTime", null },
+ { "EndSubmitTime", null },
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ ///Ƭб
+ ///
+ [Fact]
+ public async void Test_getReadingTaskList()
+ {
+ var url = $"/VisitTask/getReadingTaskList";
+
+ var jsonBody = new JObject
+ {
+ { "SubjectInfo", "" },
+ { "SiteId", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "VisitPlanArray", new JArray() },
+ { "AuditStateArray", new JArray() },
+ { "SubmitState", null },
+ { "ChallengeState", null },
+ { "BeginSubmitTime", null },
+ { "EndSubmitTime", null },
+ { "TrialId", "e4070000-3e2c-0016-2eb5-08db373d9ae2" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+ ///
+ /// IR б
+ ///
+ [Fact]
+ public async void Test_getIRUnReadSubjectTaskList()
+ {
+ var url = $"/VisitTask/getIRUnReadSubjectTaskList";
+
+ var jsonBody = new JObject
+ {
+ { "SubjectCode", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "TrialId", "c02a0000-3e2c-0016-cb0f-08db715ea9be" },
+ { "TrialReadingCriterionId", "c02a0000-3e2c-0016-e8ae-08db715ea9e4" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ [Fact]
+ public async void Test_getIRHaveReadTaskList()
+ {
+ var url = $"/VisitTask/getIRHaveReadTaskList";
+
+ var jsonBody = new JObject
+ {
+ { "SubjectCode", "" },
+ { "SortField", "" },
+ { "PageIndex", 1 },
+ { "PageSize", 20 },
+ { "TaskState", null },
+ { "ReadingCategory", null },
+ { "TrialId", "c02a0000-3e2c-0016-cb0f-08db715ea9be" },
+ { "TrialReadingCriterionId", "c02a0000-3e2c-0016-e8ae-08db715ea9e4" }
+ };
+
+ var result = await RestHelper.Post_JsonBodyRequestAsync>>(url, jsonBody);
+
+ Assert.True(result.Code != ApiResponseCodeEnum.ApiInputError && result.Code != ApiResponseCodeEnum.ProgramException);
+ }
+
+
+ #endregion
+
+
+ public void Dispose()
+ {
+ // ڲԽͷԴرӵȣ
+ _client?.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/service/EI_TestProject/EI_TestProject.csproj b/src/service/EI_TestProject/EI_TestProject.csproj
new file mode 100644
index 0000000..81db44b
--- /dev/null
+++ b/src/service/EI_TestProject/EI_TestProject.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/src/service/EI_TestProject/RestHelper.cs b/src/service/EI_TestProject/RestHelper.cs
new file mode 100644
index 0000000..6165641
--- /dev/null
+++ b/src/service/EI_TestProject/RestHelper.cs
@@ -0,0 +1,159 @@
+using Azure.Core;
+using IRaCIS.Core.Infrastructure.Extention;
+using IRaCIS.Core.Infrastructure;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using RestSharp;
+using System.Net;
+using IRaCIS.Application.Contracts;
+using System.Security.Policy;
+
+namespace EI_TestProject
+{
+ public static class RestHelper
+ {
+ private static RestClient _client { get; set; }
+
+ private static string _token { get; set; }
+
+
+
+ public static RestClient InitRestHelper(string baseUrl, string userName, string md5Pwd)
+ {
+ _client = new RestClient(baseUrl);
+
+ _client.AddDefaultHeader("Content-Type", "application/json");
+
+ var result = (LoginAndGetAccessTokenAsync>(userName, md5Pwd)).Result;
+
+ _token = result.Data.JWTStr;
+
+ return _client;
+ }
+
+
+
+ ///
+ /// ¼ȡToken
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static async Task LoginAndGetAccessTokenAsync(string userName, string md5Pwd)
+ {
+ var jsonBody = new JObject {
+ { "UserName", userName.Trim() },
+ { "Password", md5Pwd }
+ };
+
+ var result = await Post_JsonBodyRequestAsync("/user/login", jsonBody, false);
+ return result;
+ }
+
+ ///
+ /// json Post װ
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task Post_JsonBodyRequestAsync(string url, object jsonBody, bool isAddToken = true, RestClient? restClient=null)
+ {
+ var client = restClient ?? _client;
+
+ var request = new RestRequest(url, Method.Post);
+
+ if (isAddToken)
+ {
+ var accessToken = _token;
+ request.AddHeader("Authorization", "Bearer " + accessToken);
+ }
+
+ request.AddParameter("application/json", jsonBody.ToString(), ParameterType.RequestBody);
+
+ var response = await client.ExecuteAsync(request);
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ return JsonConvert.DeserializeObject(response.Content);
+ }
+ else
+ {
+ throw new Exception($"Error {response.StatusCode}: {response.StatusDescription}. {response.Content}");
+ }
+ }
+
+
+ public static async Task Put_JsonBodyRequestAsync(string url, JObject jsonBody, bool isAddToken = true, RestClient? restClient = null)
+ {
+ var client = restClient ?? _client;
+
+ var request = new RestRequest(url, Method.Put);
+
+ if (isAddToken)
+ {
+ var accessToken = _token;
+ request.AddHeader("Authorization", "Bearer " + accessToken);
+ }
+
+ request.AddParameter("application/json", jsonBody.ToString(), ParameterType.RequestBody);
+
+ var response = await client.ExecuteAsync(request);
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ return JsonConvert.DeserializeObject(response.Content);
+ }
+ else
+ {
+ throw new Exception($"Error {response.StatusCode}: {response.StatusDescription}. {response.Content}");
+ }
+ }
+
+
+ ///
+ /// URL Delete װ
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task DeleteRequestAsync(string url)
+ {
+ var request = new RestRequest(url, Method.Delete);
+ var accessToken = _token;
+ request.AddHeader("Authorization", "Bearer " + accessToken);
+
+ var response = await _client.ExecuteAsync(request);
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ return JsonConvert.DeserializeObject(response.Content);
+ }
+ else
+ {
+ throw new Exception($"Error {response.StatusCode}: {response.StatusDescription}. {response.Content}");
+ }
+ }
+
+
+ public static async Task GetRequestAsync(string url)
+ {
+ var request = new RestRequest(url, Method.Get);
+ var accessToken = _token;
+ request.AddHeader("Authorization", "Bearer " + accessToken);
+
+ var response = await _client.ExecuteAsync(request);
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ return JsonConvert.DeserializeObject(response.Content);
+ }
+ else
+ {
+ throw new Exception($"Error {response.StatusCode}: {response.StatusDescription}. {response.Content}");
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/service/EI_TestProject/Usings.cs b/src/service/EI_TestProject/Usings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/src/service/EI_TestProject/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/src/service/IRaCIS.Core.API.sln b/src/service/IRaCIS.Core.API.sln
new file mode 100644
index 0000000..5bd4843
--- /dev/null
+++ b/src/service/IRaCIS.Core.API.sln
@@ -0,0 +1,121 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.API", "IRaCIS.Core.API\IRaCIS.Core.API.csproj", "{F15CE209-6039-46A6-AE7F-E81ADA795F28}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Domain", "IRaCIS.Core.Domain\IRaCIS.Core.Domain.csproj", "{D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Application", "IRaCIS.Core.Application\IRaCIS.Core.Application.csproj", "{037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Domain.Share", "IRaCIS.Core.Domain.Share\IRaCIS.Core.Domain.Share.csproj", "{7CBC76F5-3817-46B7-8D9D-79253A89B578}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infra.EFCore", "IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj", "{6D8115E5-84D6-424B-8F8D-0C2D40347A8C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure", "IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj", "{07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EI_TestProject", "EI_TestProject\EI_TestProject.csproj", "{47F99CA7-E55B-4A0E-A511-7EDF34C57A20}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|x64.ActiveCfg = Debug|x64
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|x64.Build.0 = Debug|x64
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Debug|x86.Build.0 = Debug|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|x64.ActiveCfg = Release|x64
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|x64.Build.0 = Release|x64
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|x86.ActiveCfg = Release|Any CPU
+ {F15CE209-6039-46A6-AE7F-E81ADA795F28}.Release|x86.Build.0 = Release|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|x64.ActiveCfg = Debug|x64
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|x64.Build.0 = Debug|x64
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Debug|x86.Build.0 = Debug|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|x64.ActiveCfg = Release|x64
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|x64.Build.0 = Release|x64
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|x86.ActiveCfg = Release|Any CPU
+ {D4DF27AC-3739-4264-BFB8-AED6DC2B84C7}.Release|x86.Build.0 = Release|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|x64.ActiveCfg = Debug|x64
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|x64.Build.0 = Debug|x64
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Debug|x86.Build.0 = Debug|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|x64.ActiveCfg = Release|x64
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|x64.Build.0 = Release|x64
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|x86.ActiveCfg = Release|Any CPU
+ {037E7DE9-0AE2-4987-8C69-F51D5B9CA19D}.Release|x86.Build.0 = Release|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|x64.ActiveCfg = Debug|x64
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|x64.Build.0 = Debug|x64
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Debug|x86.Build.0 = Debug|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|x64.ActiveCfg = Release|x64
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|x64.Build.0 = Release|x64
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|x86.ActiveCfg = Release|Any CPU
+ {7CBC76F5-3817-46B7-8D9D-79253A89B578}.Release|x86.Build.0 = Release|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|x64.ActiveCfg = Debug|x64
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|x64.Build.0 = Debug|x64
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Debug|x86.Build.0 = Debug|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|x64.ActiveCfg = Release|x64
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|x64.Build.0 = Release|x64
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|x86.ActiveCfg = Release|Any CPU
+ {6D8115E5-84D6-424B-8F8D-0C2D40347A8C}.Release|x86.Build.0 = Release|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|x64.ActiveCfg = Debug|x64
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|x64.Build.0 = Debug|x64
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|x86.Build.0 = Debug|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|x64.ActiveCfg = Release|x64
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|x64.Build.0 = Release|x64
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|x86.ActiveCfg = Release|Any CPU
+ {07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|x86.Build.0 = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|x64.Build.0 = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Debug|x86.Build.0 = Debug|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|x64.ActiveCfg = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|x64.Build.0 = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|x86.ActiveCfg = Release|Any CPU
+ {47F99CA7-E55B-4A0E-A511-7EDF34C57A20}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BCC2EB19-3914-489B-B1D7-B7303E0218A3}
+ EndGlobalSection
+EndGlobal
diff --git a/src/service/IRaCIS.Core.API/.config/dotnet-tools.json b/src/service/IRaCIS.Core.API/.config/dotnet-tools.json
new file mode 100644
index 0000000..06b8682
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "5.0.9",
+ "commands": [
+ "dotnet-ef"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/service/IRaCIS.Core.API/.preview.jpg b/src/service/IRaCIS.Core.API/.preview.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/src/service/IRaCIS.Core.API/2Program.cs b/src/service/IRaCIS.Core.API/2Program.cs
new file mode 100644
index 0000000..e7649cb
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/2Program.cs
@@ -0,0 +1,231 @@
+//using Autofac;
+//using Autofac.Extensions.DependencyInjection;
+//using IRaCIS.Core.API;
+//using IRaCIS.Core.Application.Filter;
+//using IRaCIS.Core.Application.MediatR.Handlers;
+//using LogDashboard;
+//using MassTransit;
+//using MassTransit.NewIdProviders;
+//using MediatR;
+//using Microsoft.AspNetCore.Builder;
+//using Microsoft.AspNetCore.Http.Features;
+//using Microsoft.AspNetCore.HttpOverrides;
+//using Microsoft.AspNetCore.SignalR;
+//using Microsoft.Extensions.Configuration;
+//using Microsoft.Extensions.DependencyInjection;
+//using Microsoft.Extensions.Hosting;
+//using Serilog;
+//using System;
+
+//var builder = WebApplication.CreateBuilder(args);
+
+
+// //ļΪ urlȡֵ(дݲļ˾ͲҪݻ)
+// var config = new ConfigurationBuilder()
+// .AddEnvironmentVariables()
+// .Build();
+
+// var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
+
+// if (string.IsNullOrWhiteSpace(enviromentName))
+// {
+
+// var index = Array.IndexOf(args, "--env");
+// enviromentName = index > -1
+// ? args[index + 1]
+// : "Development";
+// }
+
+
+// NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
+
+
+
+
+//builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
+// .ConfigureContainer(containerBuilder =>
+// {
+// containerBuilder.RegisterModule();
+// })
+// .UseWindowsService().UseSerilog();
+
+
+
+//// Add services to the container.
+
+////ػ
+//builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
+
+//// 쳣ͳһ֤JsonлáַͳһTrim()
+//builder.Services.AddControllers(options =>
+//{
+// //options.Filters.Add();
+// options.Filters.Add();
+// options.Filters.Add();
+// //options.Filters.Add();
+
+// //if (_configuration.GetSection("BasicSystemConfig").GetValue("OpenLoginLimit"))
+// //{
+// // options.Filters.Add();
+
+// //}
+
+
+//}).AddNewtonsoftJsonSetup(); // NewtonsoftJson л
+
+////̬WebApi + UnifiedApiResultFilter ʡ
+//builder.Services.AddDynamicWebApiSetup();
+////AutoMapper
+//builder.Services.AddAutoMapperSetup();
+////EF ORM QueryWithNoLock
+//builder.Services.AddEFSetup(builder.Configuration);
+////Http Ӧѹ
+//builder.Services.AddResponseCompressionSetup();
+////Swagger Api ĵ
+//builder.Services.AddSwaggerSetup();
+////JWT Token ֤
+//builder.Services.AddJWTAuthSetup(builder.Configuration);
+//// MediatR Ϣ ¼ ӳ עhandlerӦϵ
+//builder.Services.AddMediatR(typeof(ConsistencyVerificationHandler).Assembly);
+//// EasyCaching
+//builder.Services.AddEasyCachingSetup();
+
+////services.AddDistributedMemoryCache();
+
+////// hangfire ʱ н棬Ѻ~
+//builder.Services.AddhangfireSetup(builder.Configuration);
+////// QuartZ ʱ ʹhangfire ʱãҪԴѾ
+////builder.Services.AddQuartZSetup(_configuration);
+
+//// ϴļ
+////services.AddStaticFileAuthorizationSetup();
+
+
+//////HttpReports ʱ
+////services.AddHttpReports().AddHttpTransport();
+////Serilog ־ӻ LogDashboard־
+//builder.Services.AddLogDashboardSetup();
+////ϴ
+//builder.Services.Configure(options =>
+//{
+// options.MultipartBodyLengthLimit = int.MaxValue;
+// options.ValueCountLimit = int.MaxValue;
+// options.ValueLengthLimit = int.MaxValue;
+//});
+////IP ð ߺ
+////services.AddIpPolicyRateLimitSetup(_configuration);
+////û Ȩ
+//builder.Services.AddAuthorizationPolicySetup(builder.Configuration);
+
+//builder.Services.AddJsonConfigSetup(builder.Configuration);
+
+////תͷ ȡʵIP
+//builder.Services.Configure(options =>
+//{
+// options.ForwardedHeaders =
+// ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
+//});
+////DicomӰȾͼƬ ƽ̨
+//builder.Services.AddDicomSetup();
+
+//// ʵʱӦ
+//builder.Services.AddSignalR();
+
+
+//builder.Services.AddSingleton();
+
+//builder.Services.AddControllers();
+//// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+////builder.Services.AddEndpointsApiExplorer();
+////builder.Services.AddSwaggerGen();
+
+////SerilogExtension.AddSerilogSetup(enviromentName, builder.Host.confi);
+
+
+//var app = builder.Build();
+
+//// Configure the HTTP request pipeline.
+
+////ػ
+//app.UseLocalization();
+
+//app.UseForwardedHeaders();
+
+
+////Ҫ token ʵľ̬ļ wwwroot css, JavaScript, and images don't require authentication.
+//app.UseStaticFiles();
+
+//app.UseIRacisHostStaticFileStore(app.Environment);
+
+////LogDashboard
+//app.UseLogDashboard("/LogDashboard");
+
+////hangfire
+////app.UseHangfireConfig(app.Environment);
+
+//////ʱ
+////app.UseHttpReports();
+
+////// м
+////app.UseIpRateLimiting();
+
+////Ӧѹ
+//app.UseResponseCompression();
+
+//if (app.Environment.IsDevelopment())
+//{
+// app.UseDeveloperExceptionPage();
+//}
+//else
+//{
+
+// //app.UseHsts();
+//}
+
+//SwaggerSetup.Configure(app, app.Environment);
+
+//Console.WriteLine("ǰ " + builder.Environment.EnvironmentName);
+
+////app.UseMiddleware();
+
+//// 쳣 404
+//app.UseStatusCodePagesWithReExecute("/Error/{0}");
+
+
+
+
+//////serilog ¼ûϢ
+//app.UseSerilogConfig(app.Environment);
+
+//app.UseRouting();
+
+//app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
+
+
+//app.UseAuthentication();
+////app.UseJwtBearerQueryString();
+//app.UseAuthorization();
+
+//////ļŷ Token
+//////app.UseIRacisHostStaticFileStore(env);
+
+
+
+////app.UseEndpoints(endpoints =>
+////{
+
+
+//// endpoints.MapControllers();
+
+//// endpoints.MapHub("/UploadHub")/*.RequireCors(t=>t.WithOrigins(new string[] {"null"}).AllowAnyMethod().AllowAnyHeader().AllowCredentials())*/;
+////});
+
+
+
+//app.MapControllers();
+//app.MapHub("/UploadHub")/*.RequireCors(t=>t.WithOrigins(new string[] {"null"}).AllowAnyMethod().AllowAnyHeader().AllowCredentials())*/;
+
+
+//app.Run();
+
+//ͬ
diff --git a/src/service/IRaCIS.Core.API/Controllers/ErrorController.cs b/src/service/IRaCIS.Core.API/Controllers/ErrorController.cs
new file mode 100644
index 0000000..c273d3e
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Controllers/ErrorController.cs
@@ -0,0 +1,39 @@
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Mvc;
+using Panda.DynamicWebApi.Attributes;
+
+namespace EasyCaching.Demo.Interceptors.Controllers
+{
+
+ [NonDynamicWebApi]
+ public class ErrorController : ControllerBase
+ {
+ ///
+ /// 主要处理 前端404等错误 全局业务异常已统一处理了,非业务错误会来到这里
+ ///
+ ///
+ ///
+ [Route("error/{code:int}")]
+ [HttpGet]
+ public IResponseOutput Error(int code)
+ {
+
+ if (code < 500)
+ {
+ //LogDashboard 要求返回码必须是401不能覆盖,否则 认证有问题
+ if (code == 401)
+ {
+ ControllerContext.HttpContext.Response.StatusCode = 401;
+ }
+
+ return ResponseOutput.NotOk($"Client error, actual request error status code({code})");
+ }
+ else
+ {
+
+ return ResponseOutput.NotOk($"Server error , actual request error status code({code})");
+ }
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Controllers/ExtraController.cs b/src/service/IRaCIS.Core.API/Controllers/ExtraController.cs
new file mode 100644
index 0000000..dc5fcea
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Controllers/ExtraController.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Net.Http;
+using EasyCaching.Core;
+using gRPC.ZHiZHUN.AuthServer.protos;
+using Grpc.Net.Client;
+using Grpc.Net.Client.Configuration;
+using IRaCIS.Application.Interfaces;
+using IRaCIS.Application.Contracts;
+using IRaCIS.Core.Application.Auth;
+using IRaCIS.Core.Application.Filter;
+using IRaCIS.Core.Domain.Share;
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Http;
+using IRaCIS.Core.Application.Interfaces;
+using System.Threading.Tasks;
+using IRaCIS.Application.Services;
+using IRaCIS.Core.Infra.EFCore;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Infrastructure;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+
+namespace IRaCIS.Api.Controllers
+{
+ ///
+ /// 医生基本信息 、工作信息 专业信息、审核状态
+ ///
+ [ApiController, ApiExplorerSettings(GroupName = "Reviewer")]
+ public class ExtraController : ControllerBase
+ {
+
+
+ ///
+ /// 获取医生详情
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet, Route("doctor/getDetail/{doctorId:guid}")]
+
+ public async Task> GetDoctorDetail([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService,
+ [FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService,
+ [FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService, Guid doctorId)
+ {
+ var education = await _educationService.GetEducation(doctorId);
+
+ var sowList = _doctorService.GetDoctorSowList(doctorId);
+ var ackSowList = _doctorService.GetDoctorAckSowList(doctorId);
+
+ var doctorDetail = new DoctorDetailDTO
+ {
+ AuditView =await _doctorService.GetAuditState(doctorId),
+ BasicInfoView = await _doctorService.GetBasicInfo(doctorId),
+ EmploymentView = await _doctorService.GetEmploymentInfo(doctorId),
+ AttachmentList = await attachmentService.GetAttachments(doctorId),
+
+ EducationList = education.EducationList,
+ PostgraduateList = education.PostgraduateList,
+
+ TrialExperienceView = await _trialExperienceService.GetTrialExperience(doctorId),
+ ResearchPublicationView = await _researchPublicationService.GetResearchPublication(doctorId),
+
+ SpecialtyView =await _doctorService.GetSpecialtyInfo(doctorId),
+ InHoliday = (await _vacationService.OnVacation(doctorId)).IsSuccess,
+ IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(doctorId),
+ SowList = sowList,
+ AckSowList = ackSowList
+ };
+
+ return ResponseOutput.Ok(doctorDetail);
+ }
+
+
+
+
+
+
+
+ /// 系统用户登录接口[New]
+ [HttpPost, Route("user/login")]
+ [AllowAnonymous]
+ public async Task> Login(UserLoginDTO loginUser, [FromServices] IEasyCachingProvider provider, [FromServices] IUserService _userService,
+ [FromServices] ITokenService _tokenService, [FromServices] IConfiguration configuration)
+ {
+
+ var returnModel = await _userService.Login(loginUser.UserName, loginUser.Password);
+
+ if (returnModel.IsSuccess)
+ {
+ #region GRPC 调用鉴权中心,因为服务器IIS问题 http/2 故而没法使用
+
+ ////重试策略
+ //var defaultMethodConfig = new MethodConfig
+ //{
+ // Names = { MethodName.Default },
+ // RetryPolicy = new RetryPolicy
+ // {
+ // MaxAttempts = 3,
+ // InitialBackoff = TimeSpan.FromSeconds(1),
+ // MaxBackoff = TimeSpan.FromSeconds(5),
+ // BackoffMultiplier = 1.5,
+ // RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable }
+ // }
+ //};
+
+ //#region unable to trust the certificate then the gRPC client can be configured to ignore the invalid certificate
+
+ //var httpHandler = new HttpClientHandler();
+ //// Return `true` to allow certificates that are untrusted/invalid
+ //httpHandler.ServerCertificateCustomValidationCallback =
+ // HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+
+
+ //////这一句是让grpc支持本地 http 如果本地访问部署在服务器上,那么是访问不成功的
+ //AppContext.SetSwitch(
+ // "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
+
+ //#endregion
+
+
+
+ //var grpcAdress = configuration.GetValue("GrpcAddress");
+ ////var grpcAdress = "http://localhost:7200";
+
+ //var channel = GrpcChannel.ForAddress(grpcAdress, new GrpcChannelOptions
+ //{
+ // HttpHandler = httpHandler,
+ // ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
+
+ //});
+ ////var channel = GrpcChannel.ForAddress(grpcAdress);
+ //var grpcClient = new TokenGrpcService.TokenGrpcServiceClient(channel);
+
+ //var userInfo = returnModel.Data.BasicInfo;
+
+ //var tokenResponse = grpcClient.GetUserToken(new GetTokenReuqest()
+ //{
+ // Id = userInfo.Id.ToString(),
+ // ReviewerCode = userInfo.ReviewerCode,
+ // IsAdmin = userInfo.IsAdmin,
+ // RealName = userInfo.RealName,
+ // UserTypeEnumInt = (int)userInfo.UserTypeEnum,
+ // UserTypeShortName = userInfo.UserTypeShortName,
+ // UserName = userInfo.UserName
+ //});
+
+ //returnModel.Data.JWTStr = tokenResponse.Token;
+
+ #endregion
+
+ returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo));
+
+ // 创建一个 CookieOptions 对象,用于设置 Cookie 的属性
+ var option = new CookieOptions
+ {
+ Expires = DateTime.Now.AddMonths(1), // 设置过期时间为 30 分钟之后
+ HttpOnly = false, // 确保 cookie 只能通过 HTTP 访问
+ SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None, // 设置 SameSite 属性
+ Secure = false // 确保 cookie 只能通过 HTTPS 访问
+ };
+
+ HttpContext.Response.Cookies.Append("access_token", returnModel.Data.JWTStr, option);
+
+ }
+
+ var userId = returnModel.Data.BasicInfo.Id.ToString();
+ //provider.Set(userId, userId, TimeSpan.FromMinutes(AppSettings.LoginExpiredTimeSpan));
+
+ await provider.SetAsync(userId.ToString(), returnModel.Data.JWTStr, TimeSpan.FromDays(7));
+ return returnModel;
+ }
+
+
+
+ [HttpGet, Route("imageShare/ShareImage")]
+ [AllowAnonymous]
+ public IResponseOutput ShareImage([FromServices] ITokenService _tokenService)
+ {
+ var token = _tokenService.GetToken(IRaCISClaims.Create(new UserBasicInfo()
+ {
+ Id = Guid.Empty,
+ IsReviewer = false,
+ IsAdmin = false,
+ RealName = "Share001",
+ UserName = "Share001",
+ Sex = 0,
+ //UserType = "ShareType",
+ UserTypeEnum = UserTypeEnum.ShareImage,
+ Code = "ShareCode001",
+ }));
+ return ResponseOutput.Ok("/showdicom?studyId=f7b67793-8155-0223-2f15-118f2642efb8&type=Share&token=" + token);
+ }
+
+
+
+
+
+ [HttpGet("User/UserRedirect")]
+ [AllowAnonymous]
+ public async Task UserRedirect([FromServices] IRepository _userRepository, string url ,[FromServices]ILogger _logger)
+ {
+
+ var decodeUrl = System.Web.HttpUtility.UrlDecode(url);
+
+ var userId = decodeUrl.Substring(decodeUrl.IndexOf("UserId=") + "UserId=".Length , 36) ;
+
+ var token = decodeUrl.Substring(decodeUrl.IndexOf("access_token=") + "access_token=".Length);
+
+ var domainStrList = decodeUrl.Split("/").ToList().Take(3).ToList();
+
+ var errorUrl = domainStrList[0]+"//"+ domainStrList[2]+ "/error";
+
+
+ if (!await _userRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd))
+ {
+ decodeUrl = errorUrl+ $"?ErrorMessage={System.Web.HttpUtility.UrlEncode("您的初始化链接已过期")} ";
+ }
+
+ return Redirect(decodeUrl);
+ }
+
+
+
+
+
+ [HttpGet, Route("ip")]
+ [AllowAnonymous]
+ public IResponseOutput Get([FromServices] IHttpContextAccessor _context/*, [FromServices] IUserService _userService*/)
+ {
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine($"RemoteIpAddress:{_context.HttpContext.Connection.RemoteIpAddress}");
+
+ if (Request.Headers.ContainsKey("X-Real-IP"))
+ {
+ sb.AppendLine($"X-Real-IP:{Request.Headers["X-Real-IP"].ToString()}");
+ }
+
+ if (Request.Headers.ContainsKey("X-Forwarded-For"))
+ {
+ sb.AppendLine($"X-Forwarded-For:{Request.Headers["X-Forwarded-For"].ToString()}");
+ }
+ return ResponseOutput.Ok(sb.ToString());
+ }
+
+
+ [HttpGet, Route("ip2")]
+ [AllowAnonymous]
+ public IResponseOutput Get2([FromServices] IHttpContextAccessor _context, [FromServices] IRepository _userService)
+ {
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine($"RemoteIpAddress:{_context.HttpContext.Connection.RemoteIpAddress}");
+
+ if (Request.Headers.ContainsKey("X-Real-IP"))
+ {
+ sb.AppendLine($"X-Real-IP:{Request.Headers["X-Real-IP"].ToString()}");
+ }
+
+ if (Request.Headers.ContainsKey("X-Forwarded-For"))
+ {
+ sb.AppendLine($"X-Forwarded-For:{Request.Headers["X-Forwarded-For"].ToString()}");
+ }
+ return ResponseOutput.Ok(sb.ToString());
+ }
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Controllers/FinancialChangeController.cs b/src/service/IRaCIS.Core.API/Controllers/FinancialChangeController.cs
new file mode 100644
index 0000000..cea063c
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Controllers/FinancialChangeController.cs
@@ -0,0 +1,323 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using IRaCIS.Application.Interfaces;
+using IRaCIS.Application.Contracts;
+using IRaCIS.Core.Application.Filter;
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using IRaCIS.Application.Services;
+using IRaCIS.Core.Application.Service.Inspection.DTO;
+using IRaCIS.Core.Infra.EFCore;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Application.Auth;
+
+namespace IRaCIS.Core.API.Controllers.Special
+{
+ //谨慎修改 涉及到财务模块
+
+ [ApiController, Authorize, ApiExplorerSettings(GroupName = "Financial")]
+ public class FinancialChangeController : ControllerBase
+ {
+ private readonly ITrialService _trialService;
+ private readonly ICalculateService _calculateService;
+
+ public FinancialChangeController(ITrialService trialService, ICalculateService calculateService
+ )
+ {
+ _trialService = trialService;
+ _calculateService = calculateService;
+ }
+
+
+ ////[TrialAudit(AuditType.TrialAudit, AuditOptType.AddOrUpdateTrial)]
+
+ ///// 添加实验项目-返回新增Id[AUTH]
+ ///// 新记录Id
+ //[HttpPost, Route("Inspection/trial/addOrUpdateTrial")]
+ //[UnitOfWork]
+
+ //public async Task AddOrUpdateTrialInspection(DataInspectionDto opt)
+ //{
+ // var fun =await AddOrUpdateTrial(opt.Data);
+
+ // return fun;
+ //}
+
+
+ /// 添加实验项目-返回新增Id[AUTH]
+ ///
+ /// 新记录Id
+ [HttpPost, Route("trial/addOrUpdateTrial")]
+ //[Authorize(Policy = IRaCISPolicy.PM_APM)]
+
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })]
+ public async Task> AddOrUpdateTrial(TrialCommand param, [FromServices] ITrialConfigService _ITrialConfigService)
+ {
+ var userId = Guid.Parse(User.FindFirst("id").Value);
+ var result = await _trialService.AddOrUpdateTrial(param, _ITrialConfigService);
+
+ if (_trialService.TrialExpeditedChange)
+ {
+ var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(param.Id.Value);
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
+
+ calcList.ForEach(t =>
+ {
+ if (needCalReviewerIds.Contains(t.DoctorId))
+ {
+ _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ t.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(t.YearMonth)
+ }, User.FindFirst("id").Value);
+
+ }
+ });
+ }
+
+ return result;
+ }
+
+
+ ///
+ /// 添加或更新工作量[AUTH]
+ ///
+ ///
+ ///
+ ///
+
+ [HttpPost, Route("doctorWorkload/workLoadAddOrUpdate")]
+ [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task WorkLoadAddOrUpdate([FromServices] IDoctorWorkloadService _trialWorkloadService, WorkloadCommand workLoadAddOrUpdateModel)
+ {
+ var userId = Guid.Parse(User.FindFirst("id").Value);
+ var result = await _trialWorkloadService.AddOrUpdateWorkload(workLoadAddOrUpdateModel, userId);
+ if (result.IsSuccess && workLoadAddOrUpdateModel.DataFrom == 2)
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ workLoadAddOrUpdateModel.DoctorId
+ },
+ CalculateMonth = workLoadAddOrUpdateModel.WorkTime
+ }, User.FindFirst("id").Value);
+ }
+ return result;
+ }
+
+
+
+ [HttpDelete, Route("doctorWorkload/deleteWorkLoad/{id:guid}/{trialId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task DeleteWorkLoad([FromServices] IDoctorWorkloadService _trialWorkloadService, Guid id)
+ {
+ //先判断该工作量的费用是否被锁定,如果被锁定,则不能删除
+ var workload = await _trialWorkloadService.GetWorkloadDetailById(id);
+ var yearMonth = workload.WorkTime.ToString("yyyy-MM");
+ var isLock = await _calculateService.IsLock(workload.DoctorId, yearMonth);
+
+ if (isLock)
+ {
+ return ResponseOutput.NotOk("Expenses have been settled and workload can not be reset.");
+ }
+
+ var deleteResult = await _trialWorkloadService.DeleteWorkload(id);
+ if (workload.DataFrom == (int)Domain.Share.WorkLoadFromStatus.FinalConfirm)
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ workload.DoctorId
+ },
+ CalculateMonth = workload.WorkTime
+ }, User.FindFirst("id").Value);
+ }
+ return deleteResult;
+ }
+
+
+ ///
+ /// 添加或更新汇率(会触发没有对锁定的费用计算)
+ ///
+
+ [HttpPost, Route("exchangeRate/addOrUpdateExchangeRate")]
+ public async Task AddOrUpdateExchangeRate([FromServices] IExchangeRateService _exchangeRateService, [FromServices] IPaymentAdjustmentService _costAdjustmentService, ExchangeRateCommand addOrUpdateModel)
+ {
+ var result = await _exchangeRateService.AddOrUpdateExchangeRate(addOrUpdateModel);
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, addOrUpdateModel.YearMonth);
+ foreach (var item in calcList)
+ {
+ if (item != null)
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ item.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(item.YearMonth)
+ }, User.FindFirst("id").Value);
+ }
+ }
+ await _costAdjustmentService.CalculateCNY(addOrUpdateModel.YearMonth, addOrUpdateModel.Rate);
+ return result;
+ }
+
+
+ ///
+ /// 添加或更新 职称单价[AUTH]
+ ///
+
+ [HttpPost, Route("rankPrice/addOrUpdateRankPrice")]
+ public async Task AddOrUpdateRankPrice([FromServices] IReviewerPayInfoService _reviewerPayInfoService, [FromServices] IRankPriceService _rankPriceService, RankPriceCommand addOrUpdateModel)
+ {
+ if (addOrUpdateModel.Id != Guid.Empty && addOrUpdateModel.Id != null)
+ {
+ var needCalReviewerIds =await _reviewerPayInfoService.GetReviewerIdByRankId(Guid.Parse(addOrUpdateModel.Id.ToString()));
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
+
+ foreach (var item in calcList)
+ {
+ if (item != null && needCalReviewerIds.Contains(item.DoctorId))
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ item.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(item.YearMonth)
+ }, User.FindFirst("id").Value);
+ }
+ }
+ }
+ var userId = Guid.Parse(User.FindFirst("id").Value);
+ return await _rankPriceService.AddOrUpdateRankPrice(addOrUpdateModel, userId);
+ }
+
+
+
+ ///
+ /// 添加或更新(替换)医生支付展信息[AUTH]
+ ///
+
+ [HttpPost, Route("reviewerPayInfo/addOrUpdateReviewerPayInfo")]
+ public async Task AddOrUpdateReviewerPayInfo([FromServices] IReviewerPayInfoService _doctorPayInfoService, ReviewerPayInfoCommand addOrUpdateModel)
+ {
+ var userId = Guid.Parse(User.FindFirst("id").Value);
+ var result =await _doctorPayInfoService.AddOrUpdateReviewerPayInfo(addOrUpdateModel, userId);
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(addOrUpdateModel.DoctorId, string.Empty);
+ foreach (var item in calcList)
+ {
+ if (item != null)
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ item.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(item.YearMonth)
+ }, User.FindFirst("id").Value);
+ }
+ }
+ return result;
+ }
+
+
+
+ ///
+ /// 保存(替换)项目支付价格信息(会触发没有被锁定的费用计算)[AUTH]
+ ///
+
+ [HttpPost, Route("trialPaymentPrice/addOrUpdateTrialPaymentPrice")]
+ public async Task AddOrUpdateTrialPaymentPrice([FromServices] ITrialPaymentPriceService _trialPaymentPriceService, TrialPaymentPriceCommand addOrUpdateModel)
+ {
+ var userId = Guid.Parse(User.FindFirst("id").Value);
+ var result =await _trialPaymentPriceService.AddOrUpdateTrialPaymentPrice(addOrUpdateModel);
+ var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(addOrUpdateModel.TrialId);
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
+
+ foreach (var item in calcList)
+ {
+ if (item != null && needCalReviewerIds.Contains(item.DoctorId))
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ item.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(item.YearMonth)
+ }, User.FindFirst("id").Value);
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// 批量更新奖励费用[AUTH]
+ ///
+
+ [HttpPost, Route("volumeReward/addOrUpdatevolumeRewardPriceList")]
+ public async Task AddOrUpdateAwardPriceList([FromServices] IVolumeRewardService _volumeRewardService, IEnumerable addOrUpdateModel)
+ {
+
+ var result =await _volumeRewardService.AddOrUpdateVolumeRewardPriceList(addOrUpdateModel);
+
+ var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
+ foreach (var item in calcList)
+ {
+ if (item != null)
+ {
+ await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
+ {
+ NeedCalculateReviewers = new List()
+ {
+ item.DoctorId
+ },
+ CalculateMonth = DateTime.Parse(item.YearMonth)
+ }, User.FindFirst("id").Value);
+ }
+ }
+ return result;
+ }
+
+
+
+ ///
+ /// 计算医生月度费用,并将计算的结果存入费用表
+ ///
+ [HttpPost, Route("financial/calculateMonthlyPayment")]
+ public async Task CalculateMonthlyPayment(CalculateDoctorAndMonthDTO param)
+ {
+ if (!ModelState.IsValid)
+ {
+ return ResponseOutput.NotOk("Invalid parameter.");
+ }
+ return await _calculateService.CalculateMonthlyPayment(param, User.FindFirst("id").Value);
+ }
+
+ ///
+ /// Financials /Monthly Payment 列表查询接口
+ ///
+ [HttpPost, Route("financial/getMonthlyPaymentList")]
+ public async Task> GetMonthlyPaymentList([FromServices] IPaymentService _paymentService, [FromServices] IExchangeRateService _exchangeRateService, MonthlyPaymentQueryDTO queryParam)
+ {
+ return ResponseOutput.Ok(new PaymentDTO
+ {
+ CostList = await _paymentService.GetMonthlyPaymentList(queryParam),
+ ExchangeRate = await _exchangeRateService.GetExchangeRateByMonth(queryParam.StatisticsDate.ToString("yyyy-MM"))
+ });
+ }
+
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Controllers/InspectionController.cs b/src/service/IRaCIS.Core.API/Controllers/InspectionController.cs
new file mode 100644
index 0000000..e548d07
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Controllers/InspectionController.cs
@@ -0,0 +1,460 @@
+
+using System.Threading.Tasks;
+using AutoMapper;
+
+using IRaCIS.Application.Interfaces;
+using IRaCIS.Core.Application.Contracts;
+using IRaCIS.Core.Application.Filter;
+using IRaCIS.Core.Application.Image.QA;
+using IRaCIS.Core.Application.Interfaces;
+using IRaCIS.Core.Application.Service;
+using IRaCIS.Core.Application.Service.Inspection.DTO;
+using IRaCIS.Core.Application.Service.Inspection.Interface;
+using IRaCIS.Core.Application.Service.Reading.Dto;
+using IRaCIS.Core.Application.ViewModel;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Domain.Share;
+using IRaCIS.Core.Infra.EFCore;
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+
+namespace IRaCIS.Core.API.Controllers
+{
+
+ [ApiController, ApiExplorerSettings(GroupName = "Reviewer")]
+ [UnitOfWork]
+ public class InspectionController : ControllerBase
+ {
+ private readonly IRepository _repository;
+ private readonly IMapper _mapper;
+ private readonly IUserInfo _userInfo;
+ private readonly ITrialDocumentService _trialDocumentService;
+ private readonly IQCListService _qCListService;
+ private readonly IReadingImageTaskService _iReadingImageTaskService;
+ private readonly IHttpContextAccessor _httpContext;
+ private readonly ITrialConfigService _trialConfigService;
+ private readonly INoneDicomStudyService _noneDicomStudyService;
+ private readonly ISubjectService _subjectService;
+ private readonly IReadingClinicalDataService _readingClinicalDataService;
+ private readonly ISubjectVisitService _subjectVisitService;
+ private readonly IQCOperationService _qCOperationService;
+ private readonly IClinicalDataService _clinicalDataService;
+ private readonly IVisitPlanService _visitPlanService;
+
+ private readonly IInspectionService _inspectionService;
+ private readonly IReadingMedicalReviewService _readingMedicalReviewService;
+ private readonly IReadingMedicineQuestionService _readingMedicineQuestionService;
+ private readonly IRepository _dataInspectionRepository;
+ private delegate Task executionFun(dynamic data);
+
+ public InspectionController(IRepository repository,
+ IRepository _repositoryDataInspection,
+ IMapper mapper, IUserInfo userInfo,
+ ITrialDocumentService trialDocumentService,
+ IRepository dataInspectionRepository,
+ IQCListService _qCListService,
+ IReadingImageTaskService _iReadingImageTaskService,
+ IHttpContextAccessor httpContext,
+ IInspectionService sinspectionService,
+ IReadingMedicalReviewService readingMedicalReviewService,
+ IReadingMedicineQuestionService readingMedicineQuestionService,
+ ITrialConfigService _trialConfigService,
+ INoneDicomStudyService noneDicomStudyService,
+ ISubjectService _subjectService,
+ IReadingClinicalDataService _readingClinicalDataService,
+ ISubjectVisitService subjectVisitService,
+ IQCOperationService qCOperationService,
+ IClinicalDataService clinicalDataService,
+ IVisitPlanService visitPlanService
+ )
+ {
+ this._repository = repository;
+ this._mapper = mapper;
+ this._userInfo = userInfo;
+ this._inspectionService = sinspectionService;
+ this._readingMedicalReviewService = readingMedicalReviewService;
+ this._readingMedicineQuestionService = readingMedicineQuestionService;
+ this._trialDocumentService = trialDocumentService;
+ this._qCListService = _qCListService;
+ this._iReadingImageTaskService = _iReadingImageTaskService;
+ this._httpContext = httpContext;
+ this._trialConfigService = _trialConfigService;
+ this._noneDicomStudyService = noneDicomStudyService;
+ this._subjectService = _subjectService;
+ this._readingClinicalDataService = _readingClinicalDataService;
+ this._subjectVisitService = subjectVisitService;
+ this._qCOperationService = qCOperationService;
+ this._clinicalDataService = clinicalDataService;
+ this._visitPlanService = visitPlanService;
+ this._dataInspectionRepository = dataInspectionRepository;
+ }
+
+
+
+
+ #region 获取稽查数据
+ ///
+ /// 获取稽查数据
+ ///
+ ///
+ [HttpPost, Route("Inspection/GetInspectionList")]
+ public async Task> GetInspectionList(GetDataInspectionDto dto)
+ {
+ return await _inspectionService.GetInspectionList(dto);
+ }
+ #endregion
+
+ ///
+ /// 提交肿瘤学阅片任务
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task SetOncologyReadingInfo(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _iReadingImageTaskService.SubmitOncologyReadingInfo(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+ ///
+ /// 提交Diocm阅片
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task SubmitDicomVisitTask(DataInspectionDto opt)
+ {
+
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _iReadingImageTaskService.SubmitDicomVisitTask(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 提交全局阅片任务
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task SubmitGlobalReadingInfo(DataInspectionDto opt)
+ {
+
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _iReadingImageTaskService.SubmitGlobalReadingInfo(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 项目阅片信息签名
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task TrialReadingInfoSign(DataInspectionDto opt)
+ {
+
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _trialConfigService.TrialReadingInfoSign(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 医学审核完成
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task FinishMedicalReview(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _readingMedicalReviewService.FinishMedicalReview(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+ ///
+ /// 确认项目医学审核问题
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task ConfirmReadingMedicineQuestion(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _readingMedicineQuestionService.ConfirmReadingMedicineQuestion(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 提交阅片问题
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task SubmitVisitTaskQuestions(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _iReadingImageTaskService.SubmitVisitTaskQuestions(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 提交阅片裁判问题
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ [UnitOfWork]
+ public async Task SubmitJudgeVisitTaskResult(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _iReadingImageTaskService.SubmitJudgeVisitTaskResult(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 配置 基础逻辑信息并确认
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")]
+ [UnitOfWork]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
+ public async Task ConfigTrialBasicInfoConfirm(DataInspectionDto opt)
+ {
+
+ opt.Data.IsTrialBasicLogicConfirmed = true;
+ var singid= await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _trialConfigService.ConfigTrialBasicInfo(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+
+ ///
+ /// 配置流程并确认
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialProcessInfoConfirm")]
+ [UnitOfWork]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
+ public async Task ConfigTrialProcessInfoConfirm(DataInspectionDto opt)
+ {
+ opt.Data.IsTrialProcessConfirmed = true;
+
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _trialConfigService.ConfigTrialProcessInfo(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+
+
+
+ ///
+ /// 配置加急信息并确认
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")]
+ [UnitOfWork]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
+ public async Task ConfigTrialUrgentInfoConfirm(DataInspectionDto opt)
+ {
+ opt.Data.IsTrialUrgentConfirmed = true;
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result= await _trialConfigService.ConfigTrialUrgentInfo(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+ ///
+ /// 签名确认
+ ///
+ ///
+ [HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")]
+ [UnitOfWork]
+ [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ public async Task TrialConfigSignatureConfirm(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _trialConfigService.TrialConfigSignatureConfirm(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+
+ ///
+ /// IC RequestToQC 批量提交
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task CRCRequestToQC(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _qCOperationService.CRCRequestToQC(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 设置QC 通过或者不通过 7:QC failed 8:QC passed
+ ///
+ [HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task QCPassedOrFailed(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result= await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+ ///
+ /// 一致性核查 回退 对话记录不清除 只允许PM回退
+ ///
+ [HttpPost, Route("Inspection/QCOperation/CheckBack")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task CheckBack(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _qCOperationService.CheckBack(opt.Data.Id);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 影像阅片临床数据签名
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task ReadClinicalDataSign(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _readingClinicalDataService.ReadClinicalDataSign(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// IC 设置已经重传完成
+ ///
+ [HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task SetReuploadFinished(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _qCOperationService.SetReuploadFinished(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+ ///
+ /// 更新项目状态
+ ///
+ ///
+ ///
+ [HttpPost, Route("Inspection/TrialConfig/updateTrialState")]
+ //[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
+ [UnitOfWork]
+ public async Task UpdateTrialState(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _trialConfigService.UpdateTrialState(opt.Data.trialId, opt.Data.trialStatusStr, opt.Data.reason);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 用户 签名某个文档
+ ///
+ ///
+ [HttpPost, Route("Inspection/TrialDocument/userConfirm")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+ public async Task UserConfirm(DataInspectionDto opt)
+ {
+ var singid = await _inspectionService.RecordSing(opt.SignInfo);
+ opt.Data.SignText = opt.SignInfo.SignText;
+ var result = await _trialDocumentService.UserConfirm(opt.Data);
+ await _inspectionService.CompletedSign(singid, result);
+ return result;
+ }
+
+
+ ///
+ /// 重阅同意
+ ///
+ ///
+ [HttpPost, Route("Inspection/VisitTask/ConfirmReReading")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [UnitOfWork]
+
+ public async Task ConfirmReReading(DataInspectionDto opt , [FromServices] IVisitTaskHelpeService _visitTaskCommonService,[FromServices] IVisitTaskService _visitTaskService)
+ {
+ var singId = await _inspectionService.RecordSing(opt.SignInfo);
+ var result = await _visitTaskService.ConfirmReReading(opt.Data, _visitTaskCommonService);
+ await _inspectionService.CompletedSign(singId, result);
+ return result;
+ }
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/src/service/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs
new file mode 100644
index 0000000..3a3b264
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs
@@ -0,0 +1,1203 @@
+using AutoMapper;
+using DocumentFormat.OpenXml.Drawing;
+using EasyCaching.Core;
+using ExcelDataReader;
+using IRaCIS.Application.Contracts;
+using IRaCIS.Application.Interfaces;
+using IRaCIS.Core.Application.Auth;
+using IRaCIS.Core.Application.Contracts;
+using IRaCIS.Core.Application.Contracts.Dicom;
+using IRaCIS.Core.Application.Contracts.Dicom.DTO;
+using IRaCIS.Core.Application.Filter;
+using IRaCIS.Core.Application.Helper;
+using IRaCIS.Core.Application.MediatR.CommandAndQueries;
+using IRaCIS.Core.Application.MediatR.Handlers;
+using IRaCIS.Core.Application.Service;
+using IRaCIS.Core.Application.Service.ImageAndDoc;
+using IRaCIS.Core.Application.Service.Reading.Dto;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Domain.Share;
+using IRaCIS.Core.Infra.EFCore;
+using IRaCIS.Core.Infrastructure;
+using IRaCIS.Core.Infrastructure.Extention;
+using Magicodes.ExporterAndImporter.Excel;
+using MassTransit;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+using MiniExcelLibs;
+using Newtonsoft.Json;
+using SharpCompress.Archives;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Path = System.IO.Path;
+
+namespace IRaCIS.Core.API.Controllers
+{
+
+ #region 上传基类封装
+ [DisableFormValueModelBinding]
+ public abstract class UploadBaseController : ControllerBase
+ {
+ /// 流式上传 直接返回
+ [Route("base")]
+ public virtual async Task SingleFileUploadAsync(Func filePathFunc)
+ {
+ var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
+
+ var reader = new MultipartReader(boundary, HttpContext.Request.Body);
+
+ var section = await reader.ReadNextSectionAsync();
+
+ while (section != null)
+ {
+ var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
+
+ if (hasContentDispositionHeader)
+ {
+
+ var (serverFilePath, relativePath) = filePathFunc(contentDisposition.FileName.Value);
+
+ await FileStoreHelper.WriteFileAsync(section.Body, serverFilePath);
+
+ //仅仅返回一个文件,如果多文件上传 在最后返回多个路径
+ return ResponseOutput.Ok(new
+ {
+ FilePath = relativePath,
+ FullFilePath = relativePath /*+ "?access_token=" + _userInfo.UserToken*/
+ });
+
+ }
+ section = await reader.ReadNextSectionAsync();
+ }
+ return ResponseOutput.Ok();
+ }
+
+
+ /// 流式上传 通用封装 不返回任何数据,后续还有事情处理
+ [Route("base")]
+ public virtual async Task FileUploadAsync(Func> filePathFunc)
+ {
+ var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
+
+ var reader = new MultipartReader(boundary, HttpContext.Request.Body);
+
+ var section = await reader.ReadNextSectionAsync();
+
+ while (section != null)
+ {
+ var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
+ if (hasContentDispositionHeader)
+ {
+ var fileName = contentDisposition.FileName.Value;
+ //处理压缩文件
+ if (fileName.Contains(".Zip", StringComparison.OrdinalIgnoreCase) || fileName.Contains(".rar", StringComparison.OrdinalIgnoreCase))
+ {
+ var archive = ArchiveFactory.Open(section.Body);
+
+ foreach (var entry in archive.Entries)
+ {
+ if (!entry.IsDirectory)
+ {
+ var serverFilePath = await filePathFunc(entry.Key);
+ entry.WriteToFile(serverFilePath);
+
+ }
+ }
+ }
+ //普通单个文件
+ else
+ {
+ var serverFilePath = await filePathFunc(fileName);
+
+ await FileStoreHelper.WriteFileAsync(section.Body, serverFilePath);
+ }
+
+ }
+ section = await reader.ReadNextSectionAsync();
+ }
+ }
+
+
+
+ /// 流式上传 Dicom上传
+ [Route("base")]
+ public virtual async Task DicomFileUploadAsync(Func filePathFunc, string boundary)
+ {
+
+ var fileCount = 0;
+
+ var reader = new MultipartReader(boundary, HttpContext.Request.Body);
+
+ var section = await reader.ReadNextSectionAsync();
+
+ while (section != null)
+ {
+ var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
+
+ if (hasContentDispositionHeader)
+ {
+
+ var fileName = contentDisposition.FileName.Value ?? String.Empty;
+
+ string mediaType = section.ContentType ?? String.Empty;
+
+
+ //处理压缩文件
+ if (fileName.Contains(".Zip", StringComparison.OrdinalIgnoreCase) || fileName.Contains(".rar", StringComparison.OrdinalIgnoreCase))
+ {
+ var archive = ArchiveFactory.Open(section.Body);
+
+ foreach (var entry in archive.Entries)
+ {
+ if (!entry.IsDirectory)
+ {
+ ++fileCount;
+
+ await filePathFunc(entry.Key, entry.OpenEntryStream(), fileCount);
+ }
+ }
+ }
+ //普通单个文件
+ else
+ {
+ if (mediaType.Contains("octet-stream") || mediaType.Contains("dicom"))
+ {
+ ++fileCount;
+
+ await filePathFunc(fileName, section.Body, fileCount);
+ }
+
+ }
+ }
+ section = await reader.ReadNextSectionAsync();
+ }
+
+
+
+ }
+
+
+ }
+
+ #endregion
+
+ #region Dicom 影像上传 临床数据 非diocm
+
+ [ApiExplorerSettings(GroupName = "Image")]
+ [ApiController]
+ public class StudyController : UploadBaseController
+ {
+ public IMapper _mapper { get; set; }
+ public IUserInfo _userInfo { get; set; }
+ private readonly IMediator _mediator;
+
+ private readonly IWebHostEnvironment _hostEnvironment;
+
+ private readonly IRepository _repository;
+
+ private readonly IEasyCachingProvider _provider;
+
+ public StudyController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IMediator mediator, IEasyCachingProvider provider,
+
+ IRepository repository)
+ {
+ _provider = provider;
+ _hostEnvironment = hostEnvironment;
+ _mediator = mediator;
+
+ _mapper = mapper;
+ _userInfo = userInfo;
+ _repository = repository;
+ }
+
+ [HttpPost, Route("Study/PreArchiveStudy")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task PreArchiveStudy(PreArchiveStudyCommand preArchiveStudyCommand,
+ [FromServices] IStudyService _studyService,
+ [FromServices] IRepository _studyMonitorRepository)
+ {
+
+ if (_provider.Get>(StaticData.Anonymize.Anonymize_AddFixedFiled).Value == null)
+ {
+ await _mediator.Send(new AnonymizeCacheRequest());
+ }
+
+ var savedInfo = _studyService.GetSaveToDicomInfo(preArchiveStudyCommand.SubjectVisitId);
+
+ var studyMonitor = new StudyMonitor()
+ {
+ TrialId = savedInfo.TrialId,
+ SiteId = savedInfo.SiteId,
+ SubjectId = savedInfo.SubjectId,
+ SubjectVisitId = savedInfo.SubjectVisitId,
+
+ IsSuccess = false,
+ UploadStartTime = DateTime.Now,
+ IsDicom = preArchiveStudyCommand.IsDicom,
+ IP = _userInfo.IP
+ };
+
+
+ var addEntity = await _studyMonitorRepository.AddAsync(studyMonitor, true);
+
+ return ResponseOutput.Ok(addEntity.Id);
+
+ }
+
+
+ /// Dicom 归档
+ [HttpPost, Route("Study/ArchiveStudy")]
+ [DisableFormValueModelBinding]
+ [DisableRequestSizeLimit]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task ArchiveStudyNew(/*[FromForm] ArchiveStudyCommand archiveStudyCommand,*/ Guid trialId, Guid subjectVisitId, string studyInstanceUid, Guid? abandonStudyId,Guid studyMonitorId,
+ [FromServices] ILogger _logger,
+ [FromServices] IEasyCachingProvider _provider,
+ [FromServices] IStudyService _studyService,
+ [FromServices] IHubContext _uploadHub,
+ [FromServices] IDicomArchiveService _dicomArchiveService,
+ [FromServices] IRepository _studyMonitorRepository
+ )
+ {
+
+
+
+
+
+
+ if (!HttpContext.Request.HasFormContentType ||
+ !MediaTypeHeaderValue.TryParse(HttpContext.Request.ContentType, out var mediaTypeHeader) ||
+ string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
+ {
+ return ResponseOutput.NotOk("不支持的MediaType");
+ }
+
+ var archiveStudyCommand = new ArchiveStudyCommand() { AbandonStudyId = abandonStudyId, StudyInstanceUid = studyInstanceUid, SubjectVisitId = subjectVisitId };
+
+ string studycode = string.Empty;
+
+ var startTime = DateTime.Now;
+
+ if (_provider.Exists($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}"))
+ {
+ return ResponseOutput.NotOk("当前已有人正在上传和归档该检查!");
+ }
+ else
+ {
+ _provider.Set($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}", _userInfo.Id, TimeSpan.FromMinutes(30));
+ }
+
+ //到了接口,代表上传结束了
+
+ var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == studyMonitorId);
+
+ studyMonitor.UploadFinishedTime = DateTime.Now;
+
+
+ var (archiveResult, archivedStudyIds) = (new DicomArchiveResult(), new List());
+
+
+ var (seriesInstanceUidList, sopInstanceUidList) = (new List(), new List());
+
+ //重传的时候,找出当前检查已经上传的series instance
+ if (archiveStudyCommand.AbandonStudyId != null)
+ {
+ (seriesInstanceUidList, sopInstanceUidList) = _studyService.GetHasUploadSeriesAndInstance(archiveStudyCommand.AbandonStudyId.Value);
+ }
+
+ var savedInfo = _studyService.GetSaveToDicomInfo(archiveStudyCommand.SubjectVisitId);
+
+ try
+ {
+
+ await DicomFileUploadAsync(async (fileName, fileStream, receivedCount) =>
+ {
+
+ try
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ await fileStream.CopyToAsync(memoryStream);
+
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ var (studyId, studyCode) = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo, seriesInstanceUidList, sopInstanceUidList);
+ if (!archivedStudyIds.Contains(studyId))
+ {
+ archivedStudyIds.Add(studyId);
+ archiveResult.ArchivedDicomStudies.Add(new DicomStudyBasicDTO() { StudyCode = studyCode, Id = studyId });
+ }
+ }
+
+ //await _uploadHub.Clients.All.ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount);
+
+
+ await _uploadHub.Clients.User(_userInfo.Id.ToString()).ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount);
+
+ archiveResult.ReceivedFileCount = receivedCount;
+
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e.Message + e.StackTrace);
+
+ archiveResult.ErrorFiles.Add(fileName);
+ }
+
+
+ }, mediaTypeHeader.Boundary.Value);
+
+
+ }
+ catch (Exception ex)
+ {
+ _provider.Remove($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}");
+
+ throw new BusinessValidationFailedException("请求异常,请重试!");
+ }
+
+ studyMonitor.FileSize = (decimal)HttpContext.Request.ContentLength;
+ studyMonitor.FileCount = archiveResult.ReceivedFileCount;
+ studyMonitor.FailedFileCount = archiveResult.ErrorFiles.Count;
+ studyMonitor.IsDicomReUpload = archiveStudyCommand.AbandonStudyId != null;
+ studyMonitor.Note = JsonConvert.SerializeObject(archiveResult);
+
+
+ try
+ {
+
+ if (archivedStudyIds.Count > 0) // 上传成功,处理逻辑
+ {
+
+ // 同一个检查批次 多个线程上传处理 批量保存 可能造成死锁 https://www.cnblogs.com/johnblogs/p/9945767.html
+
+ await _dicomArchiveService.DicomDBDataSaveChange();
+
+ archiveResult.ReuploadNewStudyId = archivedStudyIds[0] == archiveStudyCommand.AbandonStudyId ? archivedStudyIds[0] : Guid.Empty;
+
+ studyMonitor.IsSuccess = true;
+
+ }
+
+ }
+ catch (Exception e)
+ {
+
+ studyMonitor.Note = JsonConvert.SerializeObject(new { Message = e.Message, Result = archiveResult });
+ _logger.LogError(e.Message + e.StackTrace);
+
+ }
+ finally
+ {
+ _provider.Remove($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}");
+
+ studyMonitor.StudyId = archiveResult.ArchivedDicomStudies.FirstOrDefault()?.Id ?? Guid.Empty;
+ studyMonitor.StudyCode = archiveResult.ArchivedDicomStudies.FirstOrDefault()?.StudyCode;
+ studyMonitor.ArchiveFinishedTime = DateTime.Now;
+
+ await _studyMonitorRepository.SaveChangesAsync();
+ }
+
+
+
+ return ResponseOutput.Result(studyMonitor.IsSuccess, archiveResult);
+
+
+
+ }
+
+
+
+
+
+
+ ///
+ /// 上传临床数据 多文件
+ ///
+ ///
+ ///
+ [HttpPost("ClinicalData/UploadVisitClinicalData/{trialId:guid}/{subjectVisitId:guid}")]
+ [DisableRequestSizeLimit]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ public async Task UploadVisitClinicalData(Guid subjectVisitId)
+ {
+ await QCCommon.VerifyIsCRCSubmmitAsync(_repository, _userInfo, subjectVisitId);
+ var sv = _repository.Where(t => t.Id == subjectVisitId).Select(t => new { t.TrialId, t.SiteId, t.SubjectId }).FirstOrDefault().IfNullThrowException();
+
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetClinicalDataPath(_hostEnvironment, fileName, sv.TrialId, sv.SiteId, sv.SubjectId, subjectVisitId);
+ //插入临床pdf 路径
+ await _repository.AddAsync(new PreviousPDF()
+ {
+ SubjectVisitId = subjectVisitId,
+
+ IsVisist = true,
+ DataType = ClinicalDataType.MedicalHistory,
+ UploadType = ClinicalUploadType.PDF,
+ SubjectId = sv.SubjectId,
+ TrialId = sv.TrialId,
+ ClinicalLevel = ClinicalLevel.Subject,
+ Path = relativePath,
+ FileName = fileRealName
+ });
+ return serverFilePath;
+ });
+ await _repository.SaveChangesAsync();
+ return ResponseOutput.Ok();
+
+ }
+
+ ///
+ /// 上传临床数据模板
+ ///
+ ///
+ ///
+ [HttpPost("ClinicalData/UploadClinicalTemplate")]
+ [DisableRequestSizeLimit]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })]
+
+
+ public async Task>> UploadClinicalTemplate(Guid? trialId)
+ {
+ if (trialId == null)
+ trialId = default(Guid);
+
+ var filerelativePath = string.Empty;
+ List fileDtos = new List();
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetClinicalTemplatePath(_hostEnvironment, fileName, trialId.Value);
+ //插入临床pdf 路径
+ filerelativePath = relativePath;
+
+ fileDtos.Add(new FileDto()
+ {
+ FileName = fileName,
+ Path = relativePath
+
+ });
+ await Task.CompletedTask;
+ return serverFilePath;
+ });
+
+ return ResponseOutput.Ok(fileDtos);
+ }
+
+
+
+ ///
+ /// 上传阅片临床数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("ClinicalData/UploadClinicalData/{trialId:guid}/{subjectId:guid}/{readingId:guid}")]
+ [DisableRequestSizeLimit]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+
+ public async Task>> UploadReadClinicalData(Guid trialId, Guid subjectId, Guid readingId)
+ {
+ var filerelativePath = string.Empty;
+ List fileDtos = new List();
+
+ var siteid = await _repository.Where(x => x.Id == subjectId).Select(x => x.SiteId).FirstOrDefaultAsync();
+
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetReadClinicalDataPath(_hostEnvironment, fileName, trialId, siteid, subjectId, readingId);
+ //插入临床pdf 路径
+ filerelativePath = relativePath;
+
+ fileDtos.Add(new FileDto()
+ {
+ FileName = fileName,
+ Path = relativePath
+
+ });
+ await Task.CompletedTask;
+ return serverFilePath;
+ });
+
+ return ResponseOutput.Ok(fileDtos);
+
+ }
+
+
+ ///
+ /// 上传截图
+ ///
+ ///
+ ///
+ [HttpPost("Printscreen/UploadPrintscreen/{subjectId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task> UploadPrintscreen(Guid subjectId)
+ {
+
+ var subjectInfo = await this._repository.Where(x => x.Id == subjectId).FirstNotNullAsync();
+
+ FileDto fileDto = new FileDto();
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetUploadPrintscreenFilePath(_hostEnvironment, fileName, subjectInfo.TrialId, subjectInfo.SiteId, subjectInfo.Id);
+ fileDto.Path = relativePath;
+ fileDto.FileName = fileName;
+ await Task.CompletedTask;
+ return serverFilePath;
+ });
+
+
+ return ResponseOutput.Ok(fileDto);
+ }
+
+
+ ///
+ /// 上传Reading问题的图像
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("VisitTask/UploadReadingAnswerImage/{trialId:guid}/{visitTaskId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task> UploadReadingAnswerImage(Guid trialId, Guid visitTaskId)
+ {
+
+ FileDto fileDto = new FileDto();
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetFilePath(_hostEnvironment, fileName, trialId, visitTaskId, StaticData.Folder.JudgeTask);
+ fileDto.Path = relativePath;
+ fileDto.FileName = fileName;
+ await Task.CompletedTask;
+ return serverFilePath;
+ });
+
+
+ return ResponseOutput.Ok(fileDto);
+ }
+
+ ///
+ /// 上传裁判任务的图像
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("VisitTask/UploadJudgeTaskImage/{trialId:guid}/{visitTaskId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task> UploadJudgeTaskImage(Guid trialId, Guid visitTaskId)
+ {
+
+ FileDto fileDto = new FileDto();
+ await FileUploadAsync(async (fileName) =>
+ {
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetFilePath(_hostEnvironment, fileName, trialId, visitTaskId, StaticData.Folder.JudgeTask);
+ fileDto.Path = relativePath;
+ fileDto.FileName = fileName;
+
+ await Task.CompletedTask;
+
+ return serverFilePath;
+ });
+
+ return ResponseOutput.Ok(fileDto);
+ }
+
+
+
+ ///
+ /// 上传医学审核图片
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("TaskMedicalReview/UploadMedicalReviewImage/{trialId:guid}/{taskMedicalReviewId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task> UploadMedicalReviewImage(Guid trialId, Guid taskMedicalReviewId)
+ {
+ string path = string.Empty;
+ FileDto fileDto = new FileDto();
+ await FileUploadAsync(async (fileName) =>
+ {
+
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetMedicalReviewImage(_hostEnvironment, fileName, trialId, taskMedicalReviewId);
+
+
+ //await _repository.UpdatePartialFromQueryAsync(x => x.Id == taskMedicalReviewId, x => new TaskMedicalReview()
+ //{
+ // ImagePath = relativePath,
+ // FileName = fileName,
+ //});
+
+ path = relativePath;
+ fileDto.Path = relativePath;
+ fileDto.FileName = fileName;
+ return serverFilePath;
+ });
+
+ await _repository.SaveChangesAsync();
+ return ResponseOutput.Ok(fileDto);
+ }
+
+
+
+ ///
+ /// 上传非Dicom 文件 支持压缩包 多文件上传
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ //[DisableRequestSizeLimit]
+ [RequestSizeLimit(1_073_741_824)]
+ [HttpPost("NoneDicomStudy/UploadNoneDicomFile/{trialId:guid}/{subjectVisitId:guid}/{noneDicomStudyId:guid}/{studyMonitorId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ public async Task UploadNoneDicomFile(IFormCollection formCollection, Guid subjectVisitId, Guid noneDicomStudyId, Guid studyMonitorId,
+ [FromServices] IRepository _noneDicomStudyRepository, [FromServices] IRepository _studyMonitorRepository)
+ {
+
+ await QCCommon.VerifyIsCRCSubmmitAsync(_repository, _userInfo, subjectVisitId);
+
+ var sv = (await _repository.Where(t => t.Id == subjectVisitId).Select(t => new { t.TrialId, t.SiteId, t.SubjectId }).FirstOrDefaultAsync()).IfNullThrowConvertException();
+
+ var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == studyMonitorId);
+
+ studyMonitor.UploadFinishedTime = DateTime.Now;
+
+ await FileUploadAsync(async (fileName) =>
+ {
+
+ var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetNoneDicomFilePath(_hostEnvironment, fileName, sv.TrialId, sv.SiteId, sv.SubjectId, subjectVisitId);
+
+ await _repository.AddAsync(new NoneDicomStudyFile() { FileName = fileRealName, Path = relativePath, NoneDicomStudyId = noneDicomStudyId });
+
+ return serverFilePath;
+ });
+
+ var uploadFinishedTime = DateTime.Now;
+
+
+ //// 上传非Dicom 后 将状态改为待提交 分为普通上传 和QC后重传 普通上传时才改为待提交
+ //await _repository.UpdatePartialFromQueryAsync(t => t.Id == subjectVisitId && t.SubmitState == SubmitStateEnum.None, u => new SubjectVisit() { SubmitState = SubmitStateEnum.ToSubmit });
+
+ var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync((t => t.Id == noneDicomStudyId));
+
+ noneDicomStudy.FileCount = noneDicomStudy.FileCount + formCollection.Files.Count;
+
+ studyMonitor.FileCount = formCollection.Files.Count;
+ studyMonitor.FileSize = formCollection.Files.Sum(t => t.Length);
+ studyMonitor.IsDicom = false;
+ studyMonitor.IsDicomReUpload = false;
+ studyMonitor.StudyId = noneDicomStudyId;
+ studyMonitor.StudyCode = noneDicomStudy.StudyCode;
+ studyMonitor.ArchiveFinishedTime = DateTime.Now;
+ studyMonitor.IP = _userInfo.IP;
+
+ await _repository.SaveChangesAsync();
+
+ return ResponseOutput.Ok();
+ }
+
+
+
+
+ ///
+ /// 一致性核查 excel上传 支持三种格式
+ ///
+ ///
+ ///
+ [HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ //[Authorize(Policy = IRaCISPolicy.PM_APM)]
+ public async Task UploadVisitCheckExcel(Guid trialId)
+ {
+
+ var (serverFilePath, relativePath, fileName) = (string.Empty, string.Empty, string.Empty);
+ await FileUploadAsync(async (realFileName) =>
+ {
+ fileName = realFileName;
+
+ if (!fileName.EndsWith(".xlsx") && !fileName.EndsWith(".csv") && !fileName.EndsWith(".xls"))
+ {
+ throw new BusinessValidationFailedException("支持.xlsx、.xls、.csv格式的文件上传。");
+ }
+
+ (serverFilePath, relativePath) = FileStoreHelper.GetTrialCheckFilePath(_hostEnvironment, fileName, trialId);
+
+ await _repository.AddAsync(new ConsistencyCheckFile()
+ {
+ TrialId = trialId,
+ CreateTime = DateTime.Now,
+ FileName = fileName,
+ FilePath = relativePath,
+ RelativePath = relativePath,
+ CreateUserId = _userInfo.Id
+ });
+
+ return serverFilePath;
+ });
+
+
+
+
+ var etcCheckList = new List();
+
+
+
+ #region MiniExcel 需要自己验证数据格式规范
+
+ //if (fileName.EndsWith(".csv"))
+ //{
+ // //因为csv 需要加配置文件 不然都是null
+ // etcCheckList = MiniExcel.Query(filePath, null, configuration: config).ToList();
+ //}
+ //else if (fileName.EndsWith(".xlsx"))
+ //{
+ //
+
+
+ // etcCheckList = MiniExcel.Query(filePath).ToList();
+ //}
+
+ #endregion
+
+ //Magicodes 支持自定义特性验证
+ if (fileName.EndsWith(".xlsx"))
+ {
+ var Importer = new ExcelImporter();
+
+ var import = await Importer.Import(System.IO.File.OpenRead(serverFilePath));
+
+ if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString());
+
+ //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors));
+
+ if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors));
+
+ etcCheckList = import.Data.ToList();
+ }
+ else if (fileName.EndsWith(".csv"))
+ {
+ #region 临时方案 MiniExcel读取 然后保存为xlsx 再用 Magicodes验证数据
+
+ //因为csv 需要加配置文件 不然都是null
+ etcCheckList = MiniExcel.Query(serverFilePath, null, configuration: new MiniExcelLibs.Csv.CsvConfiguration()
+ {
+ StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312"))
+ }).ToList();
+
+ var (csVToXlsxPath, csVToXlsxRelativePath) = FileStoreHelper.GetTrialCheckFilePath(_hostEnvironment, Path.GetFileNameWithoutExtension(fileName) + ".xlsx", trialId);
+
+
+ await MiniExcel.SaveAsAsync(csVToXlsxPath, etcCheckList, excelType: ExcelType.XLSX);
+
+
+ var Importer = new ExcelImporter();
+
+ var import = await Importer.Import(System.IO.File.OpenRead(csVToXlsxPath));
+
+ if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString());
+
+ //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors));
+
+ if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors));
+
+ etcCheckList = import.Data.ToList();
+
+ #endregion
+
+
+ #region 导入组件有问题 excel编码格式
+ //var Importer = new CsvImporter();
+
+ //var import = await Importer.Import(File.OpenRead(filePath));
+
+ //if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString());
+
+ //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors));
+
+ //if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors));
+
+ //etcCheckList = import.Data.ToList();
+ #endregion
+
+ }
+ //ExcelReaderFactory 需要自己验证数据 并且从固定列取数据
+ else
+ {
+ //为了支持 xls 引入新的组件库
+ using (var stream = System.IO.File.Open(serverFilePath, FileMode.Open, FileAccess.Read))
+ {
+ // Auto-detect format, supports:
+ // - Binary Excel files (2.0-2003 format; *.xls)
+ // - OpenXml Excel files (2007 format; *.xlsx, *.xlsb)
+ using (var reader = ExcelReaderFactory.CreateReader(stream))
+ {
+
+ // 2. Use the AsDataSet extension method
+ var dateset = reader.AsDataSet();
+
+ foreach (DataRow col in dateset.Tables[0].Rows)
+ {
+
+ etcCheckList.Add(new CheckViewModel()
+ {
+ SiteCode = col[0].ToString(),
+ SubjectCode = col[1].ToString(),
+ VisitName = col[2].ToString(),
+ StudyDate = col[3].ToString(),
+ Modality = col[4].ToString(),
+ });
+ }
+
+ etcCheckList.Remove(etcCheckList[0]);
+
+ // The result of each spreadsheet is in result.Tables
+ }
+ }
+ }
+
+ if (etcCheckList == null || etcCheckList.Count == 0)
+ {
+ return ResponseOutput.NotOk("请保证上传数据符合模板文件中的样式,且存在有效数据。");
+ }
+ else
+ {
+ //处理Excel 有时只是清除某些行的数据 读取也会读到数据,只是数据是null 后面处理的时候转为字符串为报错
+ etcCheckList.ForEach(t =>
+ {
+ t.Modality = t.Modality ?? string.Empty;
+ t.SiteCode = t.SiteCode ?? string.Empty;
+ t.SubjectCode = t.SubjectCode ?? string.Empty;
+ t.VisitName = t.VisitName ?? string.Empty;
+ t.StudyDate = t.StudyDate ?? string.Empty;
+ });
+
+ var dt = DateTime.Now;
+
+ etcCheckList = etcCheckList.Where(t => !(t.Modality == string.Empty || t.SiteCode == string.Empty || t.SubjectCode == string.Empty || t.VisitName == string.Empty || t.StudyDate == string.Empty ||! DateTime.TryParse(t.StudyDate, out dt))).ToList();
+
+ if (etcCheckList.Count == 0)
+ {
+ return ResponseOutput.NotOk("请保证上传数据符合模板文件中的样式,且存在有效数据。");
+ }
+
+ }
+
+
+ await _mediator.Send(new ConsistencyVerificationRequest() { ETCList = etcCheckList, TrialId = trialId });
+
+ return ResponseOutput.Ok();
+
+ }
+ }
+
+
+ #endregion
+
+ #region 医生文件上传下载
+
+ /// 医生文件上传下载
+ [ApiExplorerSettings(GroupName = "Common")]
+ [ApiController]
+ public class FileController : UploadBaseController
+ {
+ public IMapper _mapper { get; set; }
+ public IUserInfo _userInfo { get; set; }
+
+
+ private readonly IWebHostEnvironment _hostEnvironment;
+
+ private readonly IFileService _fileService;
+
+
+
+ public FileController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IFileService fileService)
+ {
+ _fileService = fileService;
+ _hostEnvironment = hostEnvironment;
+
+ _mapper = mapper;
+ _userInfo = userInfo;
+ }
+
+ ///
+ /// 上传文件[FileUpload]
+ ///
+ /// 附件类型
+ /// 医生Id
+ /// 返回文件信息
+ [HttpPost, Route("file/UploadFile/{attachmentType}/{doctorId}")]
+ [DisableFormValueModelBinding]
+ [DisableRequestSizeLimit]
+ public async Task UploadOrdinaryFile(string attachmentType, Guid doctorId)
+ {
+
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetDoctorOrdinaryFilePath(_hostEnvironment, fileName, doctorId, attachmentType));
+
+ }
+
+
+
+
+
+ ///
+ /// 上传文件( 不是医生个人的文件)[FileUpload]
+ /// 例如:阅片章程等
+ ///
+ /// 文件类型
+ ///
+
+ [HttpPost, Route("file/UploadNonDoctorFile/{type}")]
+ [DisableFormValueModelBinding]
+ [DisableRequestSizeLimit]
+ public async Task UploadNonDoctorFile(string type)
+ {
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetNonDoctorFilePath(_hostEnvironment, fileName, type));
+ }
+
+
+ ///
+ /// 下载多个医生的所有附件
+ ///
+ ///
+ ///
+
+ [HttpPost, Route("file/downloadDoctorAttachments")]
+ public async Task> DownloadAttachment(Guid[] doctorIds)
+ {
+
+ var path = await _fileService.CreateDoctorsAllAttachmentZip(doctorIds);
+
+ return ResponseOutput.Ok(new UploadFileInfoDTO
+ {
+ FilePath = path,
+ FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7)
+
+ });
+ }
+
+ ///
+ /// 下载医生官方简历
+ ///
+ ///
+ ///
+ ///
+ [HttpPost, Route("file/downloadOfficialCV/{language}")]
+ public async Task> DownloadOfficialResume(int language, Guid[] doctorIds)
+ {
+
+ var path = await _fileService.CreateDoctorsAllAttachmentZip(doctorIds);
+ return ResponseOutput.Ok(new UploadFileInfoDTO
+ {
+ FilePath = await _fileService.CreateOfficialResumeZip(language, doctorIds),
+ FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7)
+ });
+ }
+
+ ///
+ /// 下载指定医生的指定附件
+ ///
+ /// 医生Id
+ /// 要下载的附件Id
+ ///
+ [HttpPost, Route("file/downloadByAttachmentId/{doctorId}")]
+ public async Task> DownloadAttachmentById(Guid doctorId, Guid[] attachmentIds)
+ {
+ var path = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds);
+ return ResponseOutput.Ok(new UploadFileInfoDTO
+ {
+ FilePath = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds),
+ FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7)
+ });
+ }
+
+
+
+
+ [HttpPost, Route("enroll/downloadResume/{trialId:guid}/{language}")]
+ [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
+ [AllowAnonymous]
+ public async Task> DownloadResume(int language, Guid trialId, Guid[] doctorIdArray)
+ {
+ var zipPath = await _fileService.CreateOfficialResumeZip(language, doctorIdArray);
+
+ return ResponseOutput.Ok(zipPath);
+ }
+ }
+ #endregion
+
+
+ #region 项目 系统 基本文件 上传 下载 预览
+
+ [ApiExplorerSettings(GroupName = "Common")]
+ [ApiController]
+ public class UploadDownLoadController : UploadBaseController
+ {
+ public IMapper _mapper { get; set; }
+ public IUserInfo _userInfo { get; set; }
+ private readonly IMediator _mediator;
+
+ private readonly IWebHostEnvironment _hostEnvironment;
+
+
+
+ public UploadDownLoadController(IMapper mapper, IUserInfo userInfo, IMediator mediator, IWebHostEnvironment hostEnvironment)
+ {
+ _hostEnvironment = hostEnvironment;
+ _mediator = mediator;
+ _mapper = mapper;
+ _userInfo = userInfo;
+ }
+
+ /// 缩略图
+ [AllowAnonymous]
+ [HttpGet("Common/LocalFilePreview")]
+ public async Task LocalFilePreview(string relativePath)
+ {
+
+ var _fileStorePath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, relativePath);
+
+ var storePreviewPath = _fileStorePath + ".preview.jpeg";
+
+ if (!System.IO.File.Exists(storePreviewPath))
+ {
+ ImageHelper.ResizeSave(_fileStorePath, storePreviewPath);
+ }
+
+ return new FileContentResult(await System.IO.File.ReadAllBytesAsync(storePreviewPath), "image/jpeg");
+
+ }
+
+
+ /// 通用文件下载
+ [AllowAnonymous]
+ [HttpGet("CommonDocument/DownloadCommonDoc")]
+ public async Task DownloadCommonFile(string code, [FromServices] IRepository _commonDocumentRepository)
+ {
+ var (filePath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
+
+ new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType);
+
+ return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName);
+
+ }
+
+ ///
+ /// 下载项目临床数据文件
+ ///
+ ///
+ ///
+ ///
+ [AllowAnonymous]
+ [HttpGet("CommonDocument/DownloadTrialClinicalFile")]
+ public async Task DownloadTrialClinicalFile(Guid clinicalDataTrialSetId, [FromServices] IRepository _clinicalDataTrialSetRepository)
+ {
+ var (filePath, fileName) = await FileStoreHelper.GetTrialClinicalPathAsync(_hostEnvironment, _clinicalDataTrialSetRepository, clinicalDataTrialSetId);
+
+ new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType);
+
+ return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName);
+
+ }
+
+
+ ///
+ /// 下载系统临床数据文件
+ ///
+ ///
+ ///
+ ///
+ [AllowAnonymous]
+ [HttpGet("CommonDocument/DownloadSystemClinicalFile")]
+ public async Task DownloadSystemClinicalFile(Guid clinicalDataSystemSetId, [FromServices] IRepository _clinicalDataSystemSetRepository)
+ {
+ var (filePath, fileName) = await FileStoreHelper.GetSystemClinicalPathAsync(_hostEnvironment, _clinicalDataSystemSetRepository, clinicalDataSystemSetId);
+
+ new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType);
+
+ return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName);
+
+ }
+
+ ///
+ ///上传项目签名文档
+ ///
+ ///
+ ///
+ [HttpPost("TrialDocument/UploadTrialDoc/{trialId:guid}")]
+ [DisableRequestSizeLimit]
+ [DisableFormValueModelBinding]
+ public async Task UploadTrialDoc(Guid trialId)
+ {
+
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetTrialSignDocPath(_hostEnvironment, trialId, fileName));
+
+ }
+
+ ///
+ /// 上传系统签名文档
+ ///
+ ///
+ [HttpPost("TrialDocument/UploadSystemDoc")]
+ [DisableRequestSizeLimit]
+ [DisableFormValueModelBinding]
+ public async Task UploadSysTemDoc()
+ {
+
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemSignDocPath(_hostEnvironment, fileName));
+
+ }
+
+
+ ///
+ /// 上传通用文档 比如一致性核查的 比如导出的excel 模板
+ ///
+ ///
+ [HttpPost("CommonDocument/UploadCommonDoc")]
+ [DisableRequestSizeLimit]
+ [DisableFormValueModelBinding]
+ public async Task UploadCommonDoc()
+ {
+
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetCommonDocPath(_hostEnvironment, fileName));
+ }
+
+
+ ///
+ /// 上传系统通知文档
+ ///
+ ///
+ [HttpPost("SystemNotice/UploadSystemNoticeDoc")]
+ [DisableRequestSizeLimit]
+ [DisableFormValueModelBinding]
+ public async Task UploadSystemNoticeDoc()
+ {
+
+ return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemNoticePath(_hostEnvironment, fileName));
+
+ }
+
+ }
+ #endregion
+
+}
diff --git a/src/service/IRaCIS.Core.API/GrpcToken.proto b/src/service/IRaCIS.Core.API/GrpcToken.proto
new file mode 100644
index 0000000..ace5d42
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/GrpcToken.proto
@@ -0,0 +1,69 @@
+// ʹõproto3汾
+syntax = "proto3";
+// ռ䣬ɴʱͻɶӦռ
+option csharp_namespace = "gRPC.ZHiZHUN.AuthServer.protos";
+
+/*
+ÿһҪ÷ֺŽβ
+message ͷݸʽ
+tag messageִֵֶεıʶ(tag),Ǹֵ
+*/
+
+
+// ûʱҪϢ Ϊһ
+message GetTokenReuqest{
+ string id=1;
+ string userName=2;
+ string realName=3;
+ string reviewerCode=4;
+ int32 userTypeEnumInt=5;
+ string userTypeShortName=6;
+ bool isAdmin=7;
+
+}
+
+// ʱصϢʽ
+message GetTokenResponse {
+ int32 code=1;
+ string token =2;
+}
+
+
+// service ñʶģдӦķ
+service TokenGrpcService{
+ // ȡtoken
+ rpc GetUserToken(GetTokenReuqest) returns (GetTokenResponse);
+
+}
+
+/*
+// ûʱҪϢ Ϊһ
+message AddUserReuqest{
+ string name=1;
+ int32 age=2;
+ bool isBoy=3;
+}
+// ʱصϢʽ
+message ResultResponse {
+ int32 code=1;
+ string msg =2;
+}
+//ݵIJѯϢʽΪƽʱIJѯ
+message QueryUserReuqest{
+ string name=1;
+}
+//ѯصûϢʽΪص
+message UserInfoResponse {
+ string name=1;
+ int32 age=2;
+ string gender=3;
+}
+
+// service ñʶģдӦķ
+service UserService{
+ // û
+ rpc AddUser(AddUserReuqest) returns (ResultResponse);
+ // ѯû
+ rpc GetAllUser(QueryUserReuqest) returns (UserInfoResponse);
+}
+*/
diff --git a/src/service/IRaCIS.Core.API/IRaCIS - Backup.Core.API.csproj b/src/service/IRaCIS.Core.API/IRaCIS - Backup.Core.API.csproj
new file mode 100644
index 0000000..b14f8c9
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/IRaCIS - Backup.Core.API.csproj
@@ -0,0 +1,107 @@
+
+
+
+ net6.0
+ false
+ 354572d4-9e15-4099-807c-63a2d29ff9f2
+ default
+ Linux
+
+
+
+ .\IRaCIS.Core.API.xml
+ 1701;1702;1591;
+ ..\bin\
+
+
+
+ bin\Release\IRaCIS.Core.API.xml
+ bin\Release\
+ 1701;1702;1591
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Client
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
diff --git a/src/service/IRaCIS.Core.API/IRaCIS.Core.API.csproj b/src/service/IRaCIS.Core.API/IRaCIS.Core.API.csproj
new file mode 100644
index 0000000..b7b54d1
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/IRaCIS.Core.API.csproj
@@ -0,0 +1,175 @@
+
+
+
+ net6.0
+ false
+ 354572d4-9e15-4099-807c-63a2d29ff9f2
+ default
+ Linux
+ 武汉行藏科技有限公司版权所有
+
+ 1.0.1.001
+ 医学影像处理软件
+ 1.0.1.001
+ favicon.ico
+ AnyCPU;x64
+ EI_Image_Viewer
+
+
+
+ .\IRaCIS.Core.API.xml
+ 1701;1702;1591;
+ ..\bin\
+
+
+
+ .\IRaCIS.Core.API.xml
+ 1701;1702;1591;
+ ..\bin\
+
+
+
+ .\IRaCIS.Core.API.xml
+ 1701;1702;1591;
+ ..\bin\
+
+
+
+ bin\Release\IRaCIS.Core.API.xml
+ bin\Release\
+ 1701;1702;1591
+
+
+
+ bin\Release\IRaCIS.Core.API.xml
+ bin\Release\
+ 1701;1702;1591
+
+
+
+ bin\Release\IRaCIS.Core.API.xml
+ bin\Release\
+ 1701;1702;1591
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+ Client
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/service/IRaCIS.Core.API/IRaCIS.Core.API.xml b/src/service/IRaCIS.Core.API/IRaCIS.Core.API.xml
new file mode 100644
index 0000000..999ba0b
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/IRaCIS.Core.API.xml
@@ -0,0 +1,557 @@
+
+
+
+ EI_Image_Viewer
+
+
+
+
+ 主要处理 前端404等错误 全局业务异常已统一处理了,非业务错误会来到这里
+
+
+
+
+
+
+ 医生基本信息 、工作信息 专业信息、审核状态
+
+
+
+
+ 获取医生详情
+
+
+
+
+
+
+
+
+
+
+
+ 系统用户登录接口[New]
+
+
+ 添加实验项目-返回新增Id[AUTH]
+
+ 新记录Id
+
+
+
+ 添加或更新工作量[AUTH]
+
+
+
+
+
+
+
+ 添加或更新汇率(会触发没有对锁定的费用计算)
+
+
+
+
+ 添加或更新 职称单价[AUTH]
+
+
+
+
+ 添加或更新(替换)医生支付展信息[AUTH]
+
+
+
+
+ 保存(替换)项目支付价格信息(会触发没有被锁定的费用计算)[AUTH]
+
+
+
+
+ 批量更新奖励费用[AUTH]
+
+
+
+
+ 计算医生月度费用,并将计算的结果存入费用表
+
+
+
+
+ Financials /Monthly Payment 列表查询接口
+
+
+
+
+ 获取稽查数据
+
+
+
+
+
+ 提交肿瘤学阅片任务
+
+
+
+
+
+
+ 提交Diocm阅片
+
+
+
+
+
+
+ 提交全局阅片任务
+
+
+
+
+
+
+ 项目阅片信息签名
+
+
+
+
+
+
+ 医学审核完成
+
+
+
+
+
+
+ 确认项目医学审核问题
+
+
+
+
+
+
+ 提交阅片问题
+
+
+
+
+
+
+ 提交阅片裁判问题
+
+
+
+
+
+
+ 配置 基础逻辑信息并确认
+
+
+
+
+
+
+ 配置流程并确认
+
+
+
+
+
+
+ 配置加急信息并确认
+
+
+
+
+
+
+ 签名确认
+
+
+
+
+
+ IC RequestToQC 批量提交
+
+
+
+
+
+
+ 设置QC 通过或者不通过 7:QC failed 8:QC passed
+
+
+
+
+ 一致性核查 回退 对话记录不清除 只允许PM回退
+
+
+
+
+ 影像阅片临床数据签名
+
+
+
+
+
+
+ IC 设置已经重传完成
+
+
+
+
+ 更新项目状态
+
+
+
+
+
+
+ 用户 签名某个文档
+
+
+
+
+
+ 重阅同意
+
+
+
+
+ 流式上传 直接返回
+
+
+ 流式上传 通用封装 不返回任何数据,后续还有事情处理
+
+
+ 流式上传 Dicom上传
+
+
+ Dicom 归档
+
+
+
+ 上传临床数据 多文件
+
+
+
+
+
+
+ 上传临床数据模板
+
+
+
+
+
+
+ 上传阅片临床数据
+
+
+
+
+
+
+
+
+ 上传截图
+
+
+
+
+
+
+ 上传Reading问题的图像
+
+
+
+
+
+
+
+ 上传裁判任务的图像
+
+
+
+
+
+
+
+ 上传医学审核图片
+
+
+
+
+
+
+
+ 上传非Dicom 文件 支持压缩包 多文件上传
+
+
+
+
+
+
+
+
+
+
+ 一致性核查 excel上传 支持三种格式
+
+
+
+
+
+ 医生文件上传下载
+
+
+
+ 上传文件[FileUpload]
+
+ 附件类型
+ 医生Id
+ 返回文件信息
+
+
+
+ 上传文件( 不是医生个人的文件)[FileUpload]
+ 例如:阅片章程等
+
+ 文件类型
+
+
+
+
+ 下载多个医生的所有附件
+
+
+
+
+
+
+ 下载医生官方简历
+
+
+
+
+
+
+
+ 下载指定医生的指定附件
+
+ 医生Id
+ 要下载的附件Id
+
+
+
+ 缩略图
+
+
+ 通用文件下载
+
+
+
+ 下载项目临床数据文件
+
+
+
+
+
+
+
+ 下载系统临床数据文件
+
+
+
+
+
+
+
+ 上传项目签名文档
+
+
+
+
+
+
+ 上传系统签名文档
+
+
+
+
+
+ 上传通用文档 比如一致性核查的 比如导出的excel 模板
+
+
+
+
+
+ 上传系统通知文档
+
+
+
+
+
+ IPLimit限流 启动服务
+
+
+
+
+ 创建属性
+
+ 类型
+ 序列化成员
+
+
+
+
+ 为了前端 一段时间无操作,需要重新登陆
+
+
+
+
+
+
+ 对称可逆加密
+
+
+
+
+ 用户登录成功以后,用来生成Token的方法
+
+
+
+
+
+
+
+ 非对称可逆加密
+
+
+
+
+ 从本地文件中读取用来签发 Token 的 RSA Key
+
+ 存放密钥的文件夹路径
+
+
+
+
+
+
+ 生成并保存 RSA 公钥与私钥
+
+
+
+
+
+
+ Holder for reflection information generated from Protos/GrpcToken.proto
+
+
+ File descriptor for Protos/GrpcToken.proto
+
+
+
+ 新增用户时需要传递数据消息, 可理解为一个类
+
+
+
+ Field number for the "id" field.
+
+
+ Field number for the "userName" field.
+
+
+ Field number for the "realName" field.
+
+
+ Field number for the "reviewerCode" field.
+
+
+ Field number for the "userTypeEnumInt" field.
+
+
+ Field number for the "userTypeShortName" field.
+
+
+ Field number for the "isAdmin" field.
+
+
+
+ 新增时返回的消息格式
+
+
+
+ Field number for the "code" field.
+
+
+ Field number for the "token" field.
+
+
+
+ service 用标识定义服务的,里面写对应的方法
+
+
+
+ Service descriptor
+
+
+ Client for TokenGrpcService
+
+
+ Creates a new client for TokenGrpcService
+ The channel to use to make remote calls.
+
+
+ Creates a new client for TokenGrpcService that uses a custom CallInvoker.
+ The callInvoker to use to make remote calls.
+
+
+ Protected parameterless constructor to allow creation of test doubles.
+
+
+ Protected constructor to allow creation of configured clients.
+ The client configuration.
+
+
+
+ 获取token
+
+ The request to send to the server.
+ The initial metadata to send with the call. This parameter is optional.
+ An optional deadline for the call. The call will be cancelled if deadline is hit.
+ An optional token for canceling the call.
+ The response received from the server.
+
+
+
+ 获取token
+
+ The request to send to the server.
+ The options for the call.
+ The response received from the server.
+
+
+
+ 获取token
+
+ The request to send to the server.
+ The initial metadata to send with the call. This parameter is optional.
+ An optional deadline for the call. The call will be cancelled if deadline is hit.
+ An optional token for canceling the call.
+ The call object.
+
+
+
+ 获取token
+
+ The request to send to the server.
+ The options for the call.
+ The call object.
+
+
+ Creates a new instance of client from given ClientBaseConfiguration.
+
+
+
diff --git a/src/service/IRaCIS.Core.API/Program.cs b/src/service/IRaCIS.Core.API/Program.cs
new file mode 100644
index 0000000..e58662a
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Program.cs
@@ -0,0 +1,187 @@
+using System;
+using Autofac.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Configuration;
+using Serilog;
+using MediatR;
+using IRaCIS.Core.Application.MediatR.Handlers;
+using System.Threading.Tasks;
+using MassTransit;
+using MassTransit.NewIdProviders;
+using System.IO;
+using Newtonsoft.Json.Linq;
+using System.Security.Cryptography;
+using IRaCIS.Core.Infrastructure;
+using System.Net.NetworkInformation;
+using System.Linq;
+using System.Text.Json.Nodes;
+using Microsoft.EntityFrameworkCore;
+using IRaCIS.Core.Infra.EFCore;
+using Microsoft.Data.SqlClient;
+
+namespace IRaCIS.Core.API
+{
+ public class Program
+ {
+ public readonly string environment;
+ public static async Task Main(string[] args)
+ {
+
+
+
+ try
+ {
+ //ļΪ urlȡֵ(дݲļ˾ͲҪݻ)
+ var config = new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .Build();
+
+ var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
+
+ if (string.IsNullOrWhiteSpace(enviromentName))
+ {
+
+ var index = Array.IndexOf(args, "--env");
+ enviromentName = index > -1
+ ? args[index + 1]
+ : "Development";
+ }
+
+ //Dicom
+ //ImageManager.SetImplementation(WinFormsImageManager.Instance);
+
+ var host = CreateHostBuilder(args)
+ .UseEnvironment(enviromentName) //д뻷
+ .ConfigureAppConfiguration((hostContext, config) =>
+ {
+
+ //Console.WriteLine(hostContext.HostingEnvironment.EnvironmentName);
+ config.AddJsonFile("appsettings.json", false, true)
+ .AddJsonFile($"appsettings.{enviromentName}.json", false, true);
+ })
+ .Build();
+
+
+
+ NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
+
+
+
+
+ //// Serilog
+ SerilogExtension.AddSerilogSetup(enviromentName, host.Services);
+
+
+
+ #region ֤
+
+ if (!File.Exists($@"C:\ProgramData\.xingcang\config.json"))
+ {
+ Console.WriteLine("ǰδע");
+ Log.Logger.Error("ǰδע");
+ Console.ReadLine();
+ return;
+ }
+ else
+ {
+ var json = File.ReadAllText($@"C:\ProgramData\.xingcang\config.json");
+
+ JObject jsonObject = JObject.Parse(json);
+
+ var key = jsonObject["key"].ToString();
+
+ var value = jsonObject["value"].ToString();
+
+
+ var physicalAddressList = NetworkInterface.GetAllNetworkInterfaces().Select(t => t.GetPhysicalAddress().ToString());
+
+ // жļĻǷDZ
+ if (!physicalAddressList.Contains(key))
+ {
+ Console.WriteLine("ͱӦ");
+ Log.Logger.Error("ͱӦ");
+ Console.ReadLine();
+
+ return;
+ }
+
+
+ var secrete = MD5Helper.Md5($"{key}_XINGCANG");
+
+ if (value != secrete)
+ {
+ Console.WriteLine("ע벻ƥ");
+ Log.Logger.Error("ע벻ƥ");
+ Console.ReadLine();
+
+ return;
+
+ }
+
+ }
+
+ #endregion
+
+
+
+ //Ŀ״̬
+ await InitCache(host);
+
+
+
+ host.Run();
+
+ Log.Logger.Warning($"ǰ{enviromentName}");
+
+ }
+ catch (Exception e)
+ {
+
+ Log.Logger.Error(e.InnerException is null ? e.Message + e.StackTrace : e.InnerException?.Message + e.InnerException?.StackTrace);
+ }
+
+
+
+
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .UseWindowsService()
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.ConfigureKestrel((context, options) =>
+ {
+ //Ӧ÷KestrelΪ1GB // if don't set default value is: 30 MB
+ options.Limits.MaxRequestBodySize = long.MaxValue;
+ options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30);
+ options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(20);
+
+ });
+ //webBuilder.UseSerilog();//ʱserilog,ILogger
+ webBuilder.UseStartup();
+ }).UseSerilog()
+ .UseServiceProviderFactory(new AutofacServiceProviderFactory());//ʹAutofac
+ //ConfigureLogging(logging =>
+ //{
+ // logging.ClearProviders();
+ // logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
+ //})
+ //.UseNLog()
+ // NLog: Setup NLog for Dependency injection;
+
+
+
+
+ private static async Task InitCache(IHost host)
+ {
+ var _mediator = host.Services.GetService(typeof(IMediator)) as IMediator;
+
+ await _mediator.Send(new AnonymizeCacheRequest());
+
+ await _mediator.Send(new TrialStateCacheRequest());
+ }
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Properties/launchSettings.json b/src/service/IRaCIS.Core.API/Properties/launchSettings.json
new file mode 100644
index 0000000..a5c135e
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Properties/launchSettings.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:3305",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IRaCIS.Development": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:6100"
+ },
+ "Docker": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
+ "publishAllPorts": true
+ },
+ "IRaCIS.Staging": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Staging"
+ },
+ "applicationUrl": "http://localhost:6200"
+ },
+ "IRaCIS.Production": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production"
+ },
+ "applicationUrl": "http://localhost:6300"
+ },
+ "IRaCIS.CertificateApply": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "CertificateApply"
+ },
+ "applicationUrl": "http://localhost:6400"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/service/IRaCIS.Core.API/Protos/GrpcToken.proto b/src/service/IRaCIS.Core.API/Protos/GrpcToken.proto
new file mode 100644
index 0000000..ace5d42
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Protos/GrpcToken.proto
@@ -0,0 +1,69 @@
+// ʹõproto3汾
+syntax = "proto3";
+// ռ䣬ɴʱͻɶӦռ
+option csharp_namespace = "gRPC.ZHiZHUN.AuthServer.protos";
+
+/*
+ÿһҪ÷ֺŽβ
+message ͷݸʽ
+tag messageִֵֶεıʶ(tag),Ǹֵ
+*/
+
+
+// ûʱҪϢ Ϊһ
+message GetTokenReuqest{
+ string id=1;
+ string userName=2;
+ string realName=3;
+ string reviewerCode=4;
+ int32 userTypeEnumInt=5;
+ string userTypeShortName=6;
+ bool isAdmin=7;
+
+}
+
+// ʱصϢʽ
+message GetTokenResponse {
+ int32 code=1;
+ string token =2;
+}
+
+
+// service ñʶģдӦķ
+service TokenGrpcService{
+ // ȡtoken
+ rpc GetUserToken(GetTokenReuqest) returns (GetTokenResponse);
+
+}
+
+/*
+// ûʱҪϢ Ϊһ
+message AddUserReuqest{
+ string name=1;
+ int32 age=2;
+ bool isBoy=3;
+}
+// ʱصϢʽ
+message ResultResponse {
+ int32 code=1;
+ string msg =2;
+}
+//ݵIJѯϢʽΪƽʱIJѯ
+message QueryUserReuqest{
+ string name=1;
+}
+//ѯصûϢʽΪص
+message UserInfoResponse {
+ string name=1;
+ int32 age=2;
+ string gender=3;
+}
+
+// service ñʶģдӦķ
+service UserService{
+ // û
+ rpc AddUser(AddUserReuqest) returns (ResultResponse);
+ // ѯû
+ rpc GetAllUser(QueryUserReuqest) returns (UserInfoResponse);
+}
+*/
diff --git a/src/service/IRaCIS.Core.API/SignalRHub/UploadHub.cs b/src/service/IRaCIS.Core.API/SignalRHub/UploadHub.cs
new file mode 100644
index 0000000..7fae50d
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/SignalRHub/UploadHub.cs
@@ -0,0 +1,58 @@
+using EasyCaching.Core;
+using IRaCIS.Core.Domain.Share;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Cors;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+
+namespace IRaCIS.Core.API
+{
+ public interface IUploadClient
+ {
+ Task ReceivProgressAsync(string studyInstanceUid, int haveReceivedCount);
+ }
+
+
+ public class IRaCISUserIdProvider : IUserIdProvider
+ {
+ public virtual string GetUserId(HubConnectionContext connection)
+ {
+ return connection.User?.FindFirst(JwtIRaCISClaimType.Id)?.Value!;
+ }
+ }
+
+ [AllowAnonymous]
+ [DisableCors]
+ public class UploadHub : Hub
+ {
+
+ public ILogger _logger { get; set; }
+ //public IUserInfo _userInfo { get; set; }
+ public UploadHub(/*IUserInfo userInfo,*/ ILogger logger)
+ {
+ //_userInfo = userInfo;
+
+ _logger = logger;
+ }
+
+ public override Task OnConnectedAsync()
+ {
+ //base.Context.User.id
+
+
+ _logger.LogError("连接: " + Context.ConnectionId);
+
+
+ return base.OnConnectedAsync();
+ }
+
+ //public async Task SendProgress(string studyInstanceUid, int haveReceivedCount)
+ //{
+
+
+ // await Clients.All.ReceivProgressAsync(studyInstanceUid, haveReceivedCount);
+ //}
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Startup.cs b/src/service/IRaCIS.Core.API/Startup.cs
new file mode 100644
index 0000000..b086a99
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Startup.cs
@@ -0,0 +1,231 @@
+using Autofac;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using IRaCIS.Core.Application.Filter;
+using LogDashboard;
+using MediatR;
+using IRaCIS.Core.Application.MediatR.Handlers;
+using Microsoft.Extensions.Logging;
+using AspNetCoreRateLimit;
+using Microsoft.AspNetCore.HttpOverrides;
+using IRaCIS.Core.Infra.EFCore;
+using System.Globalization;
+using Microsoft.AspNetCore.Localization;
+using Localization;
+using Magicodes.ExporterAndImporter.Core.Filters;
+using IRaCIS.Core.Application.MediatR.CommandAndQueries;
+using IRaCIS.Core.Infra.EFCore.Common;
+using Invio.Extensions.Authentication.JwtBearer;
+using Microsoft.AspNetCore.SignalR;
+using IRaCIS.Core.Domain.Share;
+
+namespace IRaCIS.Core.API
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+ public ILogger _logger { get; }
+
+ public IConfiguration _configuration { get; }
+
+ //// ConfigureContainer is where you can register things directly
+ //// with Autofac. This runs after ConfigureServices so the things
+ //// here will override registrations made in ConfigureServices.
+ //// Don't build the container; that gets done for you by the factory.
+ // for castle
+ public void ConfigureContainer(ContainerBuilder containerBuilder)
+ {
+ containerBuilder.RegisterModule();
+
+ #region Test
+ //containerBuilder.RegisterType().PropertiesAutowired().InstancePerLifetimeScope();//עִ
+
+ //var container = containerBuilder.Build();
+
+ //// Now you can resolve services using Autofac. For example,
+ //// this line will execute the lambda expression registered
+ //// to the IConfigReader service.
+ //using (var scope = container.BeginLifetimeScope())
+ //{
+ // var reader = scope.Resolve();
+
+ // var test = scope.Resolve();
+ // var test2 = scope.Resolve();
+
+ // var test3 = scope.Resolve>();
+ //}
+ #endregion
+ }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ //ػ
+ services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
+
+ // 쳣ͳһ֤JsonлáַͳһTrim()
+ services.AddControllers(options =>
+ {
+ //options.Filters.Add();
+ options.Filters.Add();
+ options.Filters.Add();
+ options.Filters.Add();
+
+ if (_configuration.GetSection("BasicSystemConfig").GetValue("OpenLoginLimit"))
+ {
+ options.Filters.Add();
+
+ }
+
+
+ })
+
+ .AddNewtonsoftJsonSetup(); // NewtonsoftJson л
+
+ services.AddOptions().Configure( _configuration.GetSection("SystemEmailSendConfig"));
+ services.AddOptions().Configure(_configuration.GetSection("BasicSystemConfig"));
+
+
+
+ //̬WebApi + UnifiedApiResultFilter ʡ
+ services.AddDynamicWebApiSetup();
+ //AutoMapper
+ services.AddAutoMapperSetup();
+ //EF ORM QueryWithNoLock
+ services.AddEFSetup(_configuration);
+ //Http Ӧѹ
+ services.AddResponseCompressionSetup();
+ //Swagger Api ĵ
+ services.AddSwaggerSetup();
+ //JWT Token ֤
+ services.AddJWTAuthSetup(_configuration);
+ // MediatR Ϣ ¼ ӳ עhandlerӦϵ
+ services.AddMediatR(typeof(ConsistencyVerificationHandler).Assembly);
+ // EasyCaching
+ services.AddEasyCachingSetup();
+
+ //services.AddDistributedMemoryCache();
+
+ // hangfire ʱ н棬Ѻ~
+ //services.AddhangfireSetup(_configuration);
+
+ // QuartZ ʱ ʹhangfire ʱãҪԴѾ
+ services.AddQuartZSetup(_configuration);
+
+ // ϴļ
+ //services.AddStaticFileAuthorizationSetup();
+
+
+ ////HttpReports ʱ
+ //services.AddHttpReports().AddHttpTransport();
+ //Serilog ־ӻ LogDashboard־
+ services.AddLogDashboardSetup();
+ //ϴ
+ services.Configure(options =>
+ {
+ options.MultipartBodyLengthLimit = int.MaxValue;
+ options.ValueCountLimit = int.MaxValue;
+ options.ValueLengthLimit = int.MaxValue;
+ });
+ //IP ð ߺ
+ //services.AddIpPolicyRateLimitSetup(_configuration);
+ // û Ȩ
+ //services.AddAuthorizationPolicySetup(_configuration);
+
+ services.AddJsonConfigSetup(_configuration);
+ //תͷ ȡʵIP
+ services.Configure(options =>
+ {
+ options.ForwardedHeaders =
+ ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
+ });
+ //DicomӰȾͼƬ ƽ̨
+ services.AddDicomSetup();
+
+ // ʵʱӦ
+ services.AddSignalR();
+
+
+ services.AddSingleton();
+
+ //services.AddSingleton();
+
+ services.AddMemoryCache();
+
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ //ػ
+ app.UseLocalization();
+
+ app.UseForwardedHeaders();
+
+ //Ӧѹ
+ app.UseResponseCompression();
+
+
+ //Ҫ token ʵľ̬ļ wwwroot css, JavaScript, and images don't require authentication.
+ app.UseStaticFiles();
+
+
+ //LogDashboard
+ app.UseLogDashboard("/LogDashboard");
+
+ ////hangfire
+ //app.UseHangfireConfig(env);
+
+
+
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ //app.UseHsts();
+ }
+ Console.WriteLine("ǰ " + env.EnvironmentName);
+
+
+ // 쳣 404
+ app.UseStatusCodePagesWithReExecute("/Error/{0}");
+
+ SwaggerSetup.Configure(app, env);
+
+
+
+ ////serilog ¼ûϢ
+ app.UseSerilogConfig(env);
+
+ app.UseRouting();
+
+ app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
+
+ app.UseIRacisHostStaticFileStore(env);
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+
+
+ endpoints.MapControllers();
+
+ endpoints.MapHub("/UploadHub");
+ });
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/EnableBufferingAttribute .cs b/src/service/IRaCIS.Core.API/Utility/EnableBufferingAttribute .cs
new file mode 100644
index 0000000..35d698c
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/EnableBufferingAttribute .cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+
+namespace IRaCIS.Core.API.Filter
+{
+ public class EnableBufferingAttribute : Attribute, IResourceFilter
+ {
+ public void OnResourceExecuting(ResourceExecutingContext context)
+ {
+ context.HttpContext.Request.EnableBuffering();
+ }
+
+ public void OnResourceExecuted(ResourceExecutedContext context)
+ {
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/FileHelpers.cs b/src/service/IRaCIS.Core.API/Utility/FileHelpers.cs
new file mode 100644
index 0000000..a77ac04
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/FileHelpers.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Net.Http.Headers;
+
+namespace IRaCIS.Core.API.Utility
+{
+ public static class FileHelpers
+ {
+ private static readonly byte[] _allowedChars = { };
+ // For more file signatures, see the File Signatures Database (https://www.filesignatures.net/)
+ // and the official specifications for the file types you wish to add.
+ private static readonly Dictionary> _fileSignature = new Dictionary>
+ {
+ { ".gif", new List { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
+ { ".png", new List { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
+ { ".jpeg", new List
+ {
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
+ }
+ },
+ { ".jpg", new List
+ {
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
+ }
+ },
+ { ".zip", new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 },
+ new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
+ new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
+ new byte[] { 0x50, 0x4B, 0x05, 0x06 },
+ new byte[] { 0x50, 0x4B, 0x07, 0x08 },
+ new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
+ }
+ },
+ };
+
+ // **WARNING!**
+ // In the following file processing methods, the file's content isn't scanned.
+ // In most production scenarios, an anti-virus/anti-malware scanner API is
+ // used on the file before making the file available to users or other
+ // systems. For more information, see the topic that accompanies this sample
+ // app.
+
+ public static async Task ProcessFormFile(IFormFile formFile,
+ ModelStateDictionary modelState, string[] permittedExtensions,
+ long sizeLimit)
+ {
+ var fieldDisplayName = string.Empty;
+
+ // Use reflection to obtain the display name for the model
+ // property associated with this IFormFile. If a display
+ // name isn't found, error messages simply won't show
+ // a display name.
+ MemberInfo property =
+ typeof(T).GetProperty(
+ formFile.Name.Substring(formFile.Name.IndexOf(".",
+ StringComparison.Ordinal) + 1));
+
+ if (property != null)
+ {
+ if (property.GetCustomAttribute(typeof(DisplayAttribute)) is
+ DisplayAttribute displayAttribute)
+ {
+ fieldDisplayName = $"{displayAttribute.Name} ";
+ }
+ }
+
+ // Don't trust the file name sent by the client. To display
+ // the file name, HTML-encode the value.
+ var trustedFileNameForDisplay = WebUtility.HtmlEncode(
+ formFile.FileName);
+
+ // Check the file length. This check doesn't catch files that only have
+ // a BOM as their content.
+ if (formFile.Length == 0)
+ {
+ modelState.AddModelError(formFile.Name,
+ $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
+
+ return new byte[0];
+ }
+
+ if (formFile.Length > sizeLimit)
+ {
+ var megabyteSizeLimit = sizeLimit / 1048576;
+ modelState.AddModelError(formFile.Name,
+ $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
+ $"{megabyteSizeLimit:N1} MB.");
+
+ return new byte[0];
+ }
+
+ try
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ await formFile.CopyToAsync(memoryStream);
+
+ // Check the content length in case the file's only
+ // content was a BOM and the content is actually
+ // empty after removing the BOM.
+ if (memoryStream.Length == 0)
+ {
+ modelState.AddModelError(formFile.Name,
+ $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
+ }
+
+ if (!IsValidFileExtensionAndSignature(
+ formFile.FileName, memoryStream, permittedExtensions))
+ {
+ modelState.AddModelError(formFile.Name,
+ $"{fieldDisplayName}({trustedFileNameForDisplay}) file " +
+ "type isn't permitted or the file's signature " +
+ "doesn't match the file's extension.");
+ }
+ else
+ {
+ return memoryStream.ToArray();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ modelState.AddModelError(formFile.Name,
+ $"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
+ $"Please contact the Help Desk for support. Error: {ex.HResult}");
+ }
+ return new byte[0];
+ }
+
+ public static async Task ProcessStreamedFile(
+ MultipartSection section, ContentDispositionHeaderValue contentDisposition,
+ ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit)
+ {
+ try
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ await section.Body.CopyToAsync(memoryStream);
+
+ // Check if the file is empty or exceeds the size limit.
+ if (memoryStream.Length == 0)
+ {
+ modelState.AddModelError("File", "The file is empty.");
+ }
+ else if (memoryStream.Length > sizeLimit)
+ {
+ var megabyteSizeLimit = sizeLimit / 1048576;
+ modelState.AddModelError("File",
+ $"The file exceeds {megabyteSizeLimit:N1} MB.");
+ }
+ else if (!IsValidFileExtensionAndSignature(
+ contentDisposition.FileName.Value, memoryStream,
+ permittedExtensions))
+ {
+ modelState.AddModelError("File",
+ "The file type isn't permitted or the file's " +
+ "signature doesn't match the file's extension.");
+ }
+ else
+ {
+ return memoryStream.ToArray();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ modelState.AddModelError("File",
+ "The upload failed. Please contact the Help Desk " +
+ $" for support. Error: {ex.HResult}");
+ // Log the exception
+ }
+
+ return new byte[0];
+ }
+
+ private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions)
+ {
+ if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
+ {
+ return false;
+ }
+
+ var ext = Path.GetExtension(fileName).ToLowerInvariant();
+
+ if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
+ {
+ return false;
+ }
+
+ data.Position = 0;
+
+ using (var reader = new BinaryReader(data))
+ {
+ if (ext.Equals(".txt") || ext.Equals(".csv") || ext.Equals(".prn"))
+ {
+ if (_allowedChars.Length == 0)
+ {
+ // Limits characters to ASCII encoding.
+ for (var i = 0; i < data.Length; i++)
+ {
+ if (reader.ReadByte() > sbyte.MaxValue)
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ // Limits characters to ASCII encoding and
+ // values of the _allowedChars array.
+ for (var i = 0; i < data.Length; i++)
+ {
+ var b = reader.ReadByte();
+ if (b > sbyte.MaxValue ||
+ !_allowedChars.Contains(b))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // Uncomment the following code block if you must permit
+ // files whose signature isn't provided in the _fileSignature
+ // dictionary. We recommend that you add file signatures
+ // for files (when possible) for all file types you intend
+ // to allow on the system and perform the file signature
+ // check.
+
+ //if (!_fileSignature.ContainsKey(ext))
+ //{
+ // return true;
+ //}
+
+
+ // File signature check
+ // --------------------
+ // With the file signatures provided in the _fileSignature
+ // dictionary, the following code tests the input content's
+ // file signature.
+
+ //var signatures = _fileSignature[ext];
+ //var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
+
+ //return signatures.Any(signature =>
+ // headerBytes.Take(signature.Length).SequenceEqual(signature));
+
+ //test
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/Jwt/CustomHSJWTService.cs b/src/service/IRaCIS.Core.API/Utility/Jwt/CustomHSJWTService.cs
new file mode 100644
index 0000000..26e6128
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/Jwt/CustomHSJWTService.cs
@@ -0,0 +1,65 @@
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
+{
+ ///
+ /// 对称可逆加密
+ ///
+ public class CustomHSJWTService : ICustomJWTService
+ {
+ #region Option注入
+ private readonly JWTTokenOptions _JWTTokenOptions;
+ public CustomHSJWTService(IOptionsMonitor jwtTokenOptions)
+ {
+ this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
+ }
+ #endregion
+
+ ///
+ /// 用户登录成功以后,用来生成Token的方法
+ ///
+ ///
+ ///
+ ///
+ public string GetToken(string UserName, string password)
+ {
+ #region 有效载荷,大家可以自己写,爱写多少写多少;尽量避免敏感信息
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.Name, UserName),
+ new Claim("NickName",UserName),
+ new Claim("Role","Administrator"),//传递其他信息
+ new Claim("ABCC","ABCC"),
+ new Claim("ABCCDDDDD","ABCCDDDDD"),
+ new Claim("Student","甜酱油")
+ };
+
+ //需要加密:需要加密key:
+ //Nuget引入:Microsoft.IdentityModel.Tokens
+ SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_JWTTokenOptions.SecurityKey));
+
+ SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+
+ //Nuget引入:System.IdentityModel.Tokens.Jwt
+ JwtSecurityToken token = new JwtSecurityToken(
+ issuer: _JWTTokenOptions.Issuer,
+ audience: _JWTTokenOptions.Audience,
+ claims: claims,
+ expires: DateTime.Now.AddMinutes(5),//5分钟有效期
+ signingCredentials: creds);
+
+ string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
+ return returnToken;
+ #endregion
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/Jwt/CustomRSSJWTervice.cs b/src/service/IRaCIS.Core.API/Utility/Jwt/CustomRSSJWTervice.cs
new file mode 100644
index 0000000..b93b444
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/Jwt/CustomRSSJWTervice.cs
@@ -0,0 +1,60 @@
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.IO;
+using System.Linq;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
+{
+ ///
+ /// 非对称可逆加密
+ ///
+ public class CustomRSSJWTervice : ICustomJWTService
+
+ {
+ #region Option注入
+ private readonly JWTTokenOptions _JWTTokenOptions;
+ public CustomRSSJWTervice(IOptionsMonitor jwtTokenOptions)
+ {
+ this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
+ }
+ #endregion
+
+ public string GetToken(string userName, string password)
+ {
+ #region 使用加密解密Key 非对称
+ string keyDir = Directory.GetCurrentDirectory();
+ if (RSAHelper.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
+ {
+ keyParams = RSAHelper.GenerateAndSaveKey(keyDir);
+ }
+ #endregion
+
+ //string jtiCustom = Guid.NewGuid().ToString();//用来标识 Token
+ Claim[] claims = new[]
+ {
+ new Claim(ClaimTypes.Name, userName),
+ new Claim(ClaimTypes.Role,"admin"),
+ new Claim("password",password)
+ };
+
+ SigningCredentials credentials = new SigningCredentials(new RsaSecurityKey(keyParams), SecurityAlgorithms.RsaSha256Signature);
+
+ var token = new JwtSecurityToken(
+ issuer: this._JWTTokenOptions.Issuer,
+ audience: this._JWTTokenOptions.Audience,
+ claims: claims,
+ expires: DateTime.Now.AddMinutes(60),//5分钟有效期
+ signingCredentials: credentials);
+
+ var handler = new JwtSecurityTokenHandler();
+ string tokenString = handler.WriteToken(token);
+ return tokenString;
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/Jwt/ICustomJWTService.cs b/src/service/IRaCIS.Core.API/Utility/Jwt/ICustomJWTService.cs
new file mode 100644
index 0000000..ac1af95
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/Jwt/ICustomJWTService.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
+{
+ public interface ICustomJWTService
+ {
+ string GetToken(string UserName, string password);
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/Jwt/JWTTokenOptions.cs b/src/service/IRaCIS.Core.API/Utility/Jwt/JWTTokenOptions.cs
new file mode 100644
index 0000000..e8a0876
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/Jwt/JWTTokenOptions.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
+{
+ public class JWTTokenOptions
+ {
+ public string Audience
+ {
+ get;
+ set;
+ }
+ public string SecurityKey
+ {
+ get;
+ set;
+ }
+ //public SigningCredentials Credentials
+ //{
+ // get;
+ // set;
+ //}
+ public string Issuer
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/Jwt/RSAHelper.cs b/src/service/IRaCIS.Core.API/Utility/Jwt/RSAHelper.cs
new file mode 100644
index 0000000..707ba46
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/Jwt/RSAHelper.cs
@@ -0,0 +1,61 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
+{
+ public class RSAHelper
+ {
+ ///
+ /// 从本地文件中读取用来签发 Token 的 RSA Key
+ ///
+ /// 存放密钥的文件夹路径
+ ///
+ ///
+ ///
+ public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters)
+ {
+ string filename = withPrivate ? "key.json" : "key.public.json";
+ string fileTotalPath = Path.Combine(filePath, filename);
+ keyParameters = default(RSAParameters);
+ if (!File.Exists(fileTotalPath))
+ {
+ return false;
+ }
+ else
+ {
+ keyParameters = JsonConvert.DeserializeObject(File.ReadAllText(fileTotalPath));
+ return true;
+ }
+ }
+ ///
+ /// 生成并保存 RSA 公钥与私钥
+ ///
+ ///
+ ///
+ ///
+ public static RSAParameters GenerateAndSaveKey(string filePath, bool withPrivate = true)
+ {
+ RSAParameters publicKeys, privateKeys;
+ using (var rsa = new RSACryptoServiceProvider(2048))//即时生成
+ {
+ try
+ {
+ privateKeys = rsa.ExportParameters(true);
+ publicKeys = rsa.ExportParameters(false);
+ }
+ finally
+ {
+ rsa.PersistKeyInCsp = false;
+ }
+ }
+ File.WriteAllText(Path.Combine(filePath, "key.json"), JsonConvert.SerializeObject(privateKeys));
+ File.WriteAllText(Path.Combine(filePath, "key.public.json"), JsonConvert.SerializeObject(publicKeys));
+ return withPrivate ? privateKeys : publicKeys;
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/Utility/MultipartRequestHelper.cs b/src/service/IRaCIS.Core.API/Utility/MultipartRequestHelper.cs
new file mode 100644
index 0000000..6be5c7b
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/Utility/MultipartRequestHelper.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using Microsoft.Net.Http.Headers;
+
+namespace IRaCIS.Core.API.Utility
+{
+ public static class MultipartRequestHelper
+ {
+ // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
+ // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
+ public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
+ {
+ var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
+
+ if (string.IsNullOrWhiteSpace(boundary))
+ {
+ throw new InvalidDataException("Missing content-type boundary.");
+ }
+
+ if (boundary.Length > lengthLimit)
+ {
+ throw new InvalidDataException(
+ $"Multipart boundary length limit {lengthLimit} exceeded.");
+ }
+
+ return boundary;
+ }
+
+ public static bool IsMultipartContentType(string contentType)
+ {
+ return !string.IsNullOrEmpty(contentType)
+ && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
+ }
+
+ public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
+ {
+ // Content-Disposition: form-data; name="key";
+ return contentDisposition != null
+ && contentDisposition.DispositionType.Equals("form-data")
+ && string.IsNullOrEmpty(contentDisposition.FileName.Value)
+ && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
+ }
+
+ public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
+ {
+ // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
+ return contentDisposition != null
+ && contentDisposition.DispositionType.Equals("form-data")
+ && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
+ || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/AuthMiddleware.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/AuthMiddleware.cs
new file mode 100644
index 0000000..9f0a733
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/AuthMiddleware.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IdentityModel.Tokens.Jwt;
+using System.Net;
+using System.Threading.Tasks;
+using EasyCaching.Core;
+using IRaCIS.Core.Domain.Share;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Http;
+
+namespace IRaCIS.WX.CoreApi.Auth
+{
+ public class AuthMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IEasyCachingProvider _provider;
+ public AuthMiddleware(RequestDelegate next, IEasyCachingProvider provider)
+ {
+ _next = next;
+ _provider = provider;
+ }
+ ///
+ ///为了前端 一段时间无操作,需要重新登陆
+ ///
+ ///
+ ///
+ public async Task Invoke(HttpContext httpContext)
+ {
+
+ var isLogin = httpContext.Request.Path.ToString().ToLower().Contains("login");
+
+ var result = await httpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
+
+ if (!isLogin)
+ {
+ if (!result.Succeeded)
+ {
+ httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ await httpContext.Response.WriteAsync("Unauthorized");
+ }
+ else
+ {
+ var toekn = result.Properties.Items[".Token.access_token"];
+ var jwtHandler = new JwtSecurityTokenHandler();
+ JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(toekn);
+ object userId;
+ jwtToken.Payload.TryGetValue("id", out userId);
+
+ var cacheValueExist = await _provider.ExistsAsync(userId.ToString()); //Get(userId.ToString()).ToString();
+ if (!cacheValueExist)
+ {
+ httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ await httpContext.Response.WriteAsync("Unauthorized");
+ }
+ else
+ {
+ await _provider.SetAsync(userId.ToString(), userId.ToString(), TimeSpan.FromMinutes(15));
+ httpContext.User = result.Principal;
+ await _next.Invoke(httpContext);
+ }
+ }
+ }
+ else await _next.Invoke(httpContext);
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/HangfireConfig.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/HangfireConfig.cs
new file mode 100644
index 0000000..4842353
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/HangfireConfig.cs
@@ -0,0 +1,40 @@
+using Hangfire;
+using Hangfire.Dashboard;
+using IRaCIS.Application.Services.BackGroundJob;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+
+namespace IRaCIS.Core.API
+{
+
+
+ public static class HangfireConfig
+ {
+
+ public static void UseHangfireConfig(this IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ app.UseHangfireDashboard("/api/hangfire", new DashboardOptions()
+ {
+ //直接访问,没有带token 获取不到用户身份信息,所以这种自定义授权暂时没法使用
+ //Authorization = new[] { new hangfireAuthorizationFilter() }
+
+ //本地请求 才能看
+ Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() }
+
+ });
+
+ #region hangfire
+ //// 延迟任务执行 1秒之后执行 有时启动没运行 换成添加到队列中
+ //BackgroundJob.Schedule(t => t.MemoryCacheTrialStatus(), TimeSpan.FromSeconds(1));
+ ////添加到后台任务队列,
+ //BackgroundJob.Enqueue(t => t.MemoryCacheTrialStatus());
+
+ //周期性任务,1天执行一次
+
+ RecurringJob.AddOrUpdate(t => t.ProjectStartCache(), Cron.Daily);
+
+ #endregion
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/hangfireAuthorizationFilter.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/hangfireAuthorizationFilter.cs
new file mode 100644
index 0000000..707b552
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/Hangfire/hangfireAuthorizationFilter.cs
@@ -0,0 +1,17 @@
+using Hangfire.Dashboard;
+
+namespace IRaCIS.Core.API.Filter
+{
+ public class hangfireAuthorizationFilter : IDashboardAuthorizationFilter
+ {
+ public bool Authorize(DashboardContext context)
+ {
+ var httpContext = context.GetHttpContext();
+
+ // Allow all authenticated users to see the Dashboard (potentially dangerous).
+ return httpContext.User.Identity.IsAuthenticated;
+
+ //return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/IRacisHostFileStoreConfig.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/IRacisHostFileStoreConfig.cs
new file mode 100644
index 0000000..449c48d
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/IRacisHostFileStoreConfig.cs
@@ -0,0 +1,38 @@
+using IRaCIS.Core.Application.Helper;
+using IRaCIS.Core.Domain.Share;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.FileProviders;
+using System.IO;
+
+namespace IRaCIS.Core.API
+{
+ public static class IRacisHostFileStore
+ {
+
+ public static void UseIRacisHostStaticFileStore(this IApplicationBuilder app, IWebHostEnvironment _hostEnvironment)
+ {
+
+ var iRaCISDataFolder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
+
+ if (!Directory.Exists(iRaCISDataFolder))
+ {
+ Directory.CreateDirectory(iRaCISDataFolder);
+ }
+
+ app.UseStaticFiles(new StaticFileOptions
+ {
+ FileProvider = new PhysicalFileProvider(iRaCISDataFolder),
+ RequestPath = $"/{StaticData.Folder.IRaCISDataFolder}",
+ ServeUnknownFileTypes = true,
+ DefaultContentType = "application/octet-stream"
+
+ // // Set up custom content types - associating file extension to MIME type
+ //var provider = new FileExtensionContentTypeProvider();
+ // // Add new mappings
+ // provider.Mappings[".myapp"] = "application/x-msdownload";
+
+ });
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/LocalizationConfig.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/LocalizationConfig.cs
new file mode 100644
index 0000000..45ab1ec
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/LocalizationConfig.cs
@@ -0,0 +1,34 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Localization;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace IRaCIS.Core.API
+{
+ public static class LocalizationConfig
+ {
+
+ public static void UseLocalization(this IApplicationBuilder app)
+ {
+ var supportedCultures = new List
+ {
+ new CultureInfo("en-US"),
+ new CultureInfo("zh-CN")
+ };
+
+ var options = new RequestLocalizationOptions
+ {
+ //DefaultRequestCulture = new RequestCulture("en-US"),
+ SupportedCultures = supportedCultures,
+ SupportedUICultures = supportedCultures,
+
+ ApplyCurrentCultureToResponseHeaders = true
+ };
+
+ //options.RequestCultureProviders.RemoveAt(0);
+ //options.RequestCultureProviders.RemoveAt(1);
+
+ app.UseRequestLocalization(options);
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/LogDashboard/LogDashBoardAuthFilter.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/LogDashboard/LogDashBoardAuthFilter.cs
new file mode 100644
index 0000000..2fd35ea
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/LogDashboard/LogDashBoardAuthFilter.cs
@@ -0,0 +1,15 @@
+using LogDashboard;
+using LogDashboard.Authorization;
+
+namespace IRaCIS.Core.API.Filter
+{
+
+ public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter
+ {
+ //在此可以利用 本系统的UerTypeEnum 判断
+ public bool Authorization(LogDashboardContext context)
+ {
+ return context.HttpContext.User.Identity.IsAuthenticated;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs
new file mode 100644
index 0000000..112314b
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs
@@ -0,0 +1,106 @@
+using Microsoft.AspNetCore.Http;
+using Serilog;
+using Serilog.Context;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace IRaCIS.Core.API._PipelineExtensions.Serilog
+{
+ public class RequestResponseLoggingMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public RequestResponseLoggingMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ var ContentType = context.Request.ContentType;
+
+ var isUploadRelation = false;
+
+ if (ContentType != null)
+ {
+ isUploadRelation = context.Request.ContentType.Contains("multipart/form-data") || context.Request.ContentType.Contains("boundary") || context.Request.ContentType.Contains("octet-stream");
+ }
+
+
+ if (isUploadRelation)
+ {
+ using (LogContext.PushProperty("RequestBody", string.Empty))
+ {
+ await _next.Invoke(context);
+ }
+ }
+ else
+ {
+
+ var requestBodyPayload = await ReadRequestBody(context.Request);
+
+ using (LogContext.PushProperty("RequestBody", requestBodyPayload))
+ {
+ //await _next.Invoke()
+ await _next.Invoke(context);
+ }
+ }
+
+ //var request = context.Request;
+
+
+ // Read and log request body data
+
+ //SerilogHelper.RequestPayload = requestBodyPayload;
+
+ #region ResponseBody
+
+ //// Read and log response body data
+ //// Copy a pointer to the original response body stream
+ //var originalResponseBodyStream = context.Response.Body;
+
+ //// Create a new memory stream...
+ //using (var responseBody = new MemoryStream())
+ //{
+ // // ...and use that for the temporary response body
+ // context.Response.Body = responseBody;
+
+ // // Continue down the Middleware pipeline, eventually returning to this class
+ //await _next(context);
+
+ // // Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
+ // await responseBody.CopyToAsync(originalResponseBodyStream);
+ //}
+ //string responseBodyPayload = await ReadResponseBody(context.Response);
+ //_diagnosticContext.Set("ResponseBody", responseBodyPayload);
+
+ #endregion
+
+
+ }
+
+ private async Task ReadRequestBody(HttpRequest request)
+ {
+ // Ensure the request's body can be read multiple times (for the next middlewares in the pipeline).
+ request.EnableBuffering();
+
+ using var streamReader = new StreamReader(request.Body, leaveOpen: true);
+ var requestBody = await streamReader.ReadToEndAsync();
+
+ // Reset the request's body stream position for next middleware in the pipeline.
+ request.Body.Position = 0;
+ return requestBody == null ? String.Empty : requestBody.Trim();
+ }
+
+ private static async Task ReadResponseBody(HttpResponse response)
+ {
+ response.Body.Seek(0, SeekOrigin.Begin);
+ string responseBody = await new StreamReader(response.Body).ReadToEndAsync();
+ response.Body.Seek(0, SeekOrigin.Begin);
+
+ return $"{responseBody}";
+ }
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs
new file mode 100644
index 0000000..c5aab64
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs
@@ -0,0 +1,30 @@
+using Hangfire;
+using Hangfire.Dashboard;
+using IRaCIS.Core.API._PipelineExtensions.Serilog;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Serilog;
+
+namespace IRaCIS.Core.API
+{
+
+
+ public static class SerilogConfig
+ {
+
+ public static void UseSerilogConfig(this IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ app.UseMiddleware();
+
+ app.UseSerilogRequestLogging(opts
+ =>
+ {
+ opts.MessageTemplate = "{TokenUserRealName} {TokenUserType} {ClientIp} {RequestIP} {Host} {Protocol} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms";
+ opts.EnrichDiagnosticContext = SerilogHelper.EnrichFromRequest;
+ });
+
+
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogHelper.cs b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogHelper.cs
new file mode 100644
index 0000000..1bb30e7
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogHelper.cs
@@ -0,0 +1,58 @@
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Http;
+using Serilog;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IRaCIS.Core.API
+{
+ public class SerilogHelper
+ {
+ //public static string RequestPayload = "";
+
+ public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
+ {
+ var request = httpContext.Request;
+
+ // Set all the common properties available for every request
+ diagnosticContext.Set("Host", request.Host);
+
+ //这种获取的Ip不准 配置服务才行
+ diagnosticContext.Set("RequestIP", httpContext.Connection.RemoteIpAddress.ToString());
+
+ //这种方式可以,但是serilog提供了 就不用了
+ //diagnosticContext.Set("TestIP", httpContext.GetUserIp());
+
+ diagnosticContext.Set("Protocol", request.Protocol);
+ diagnosticContext.Set("Scheme", request.Scheme);
+
+ //这种方式不行 读取的body为空字符串 必须在中间件中读取
+ //diagnosticContext.Set("RequestBody", await ReadRequestBody(httpContext.Request));
+ //diagnosticContext.Set("RequestBody", RequestPayload);
+
+ // Only set it if available. You're not sending sensitive data in a querystring right?!
+ if (request.QueryString.HasValue)
+ {
+ diagnosticContext.Set("QueryString", request.QueryString.Value);
+ }
+
+ // Set the content-type of the Response at this point
+ diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
+
+ diagnosticContext.Set("TokenUserRealName", httpContext?.User?.FindFirst("realName")?.Value);
+
+ diagnosticContext.Set("TokenUserType", httpContext?.User?.FindFirst("userTypeEnumName")?.Value);
+
+ // Retrieve the IEndpointFeature selected for the request
+ var endpoint = httpContext.GetEndpoint();
+ if (endpoint is object) // endpoint != null
+ {
+ diagnosticContext.Set("EndpointName", endpoint.DisplayName);
+ }
+ }
+
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/ApiResponseHandler.cs b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/ApiResponseHandler.cs
new file mode 100644
index 0000000..32f9995
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/ApiResponseHandler.cs
@@ -0,0 +1,38 @@
+using IRaCIS.Core.Infrastructure.Extention;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using System;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+
+namespace IRaCIS.Core.API
+{
+ public class ApiResponseHandler : AuthenticationHandler
+ {
+ public ApiResponseHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
+ {
+ }
+
+ protected override Task HandleAuthenticateAsync()
+ {
+ throw new NotImplementedException();
+ }
+ protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
+ {
+ Response.ContentType = "application/json";
+ Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk("您无权访问该接口", ApiResponseCodeEnum.NoToken)));
+ }
+
+ protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
+ {
+ Response.ContentType = "application/json";
+ Response.StatusCode = StatusCodes.Status403Forbidden;
+ await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk("您的权限不允许进行该操作",ApiResponseCodeEnum.HaveTokenNotAccess)));
+ }
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/AuthorizationPolicySetup.cs b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/AuthorizationPolicySetup.cs
new file mode 100644
index 0000000..8e3ec14
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/AuthorizationPolicySetup.cs
@@ -0,0 +1,82 @@
+using IRaCIS.Core.Application.Auth;
+using IRaCIS.Core.Domain.Share;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace IRaCIS.Core.API
+{
+ public static class AuthorizationPolicySetup
+ {
+
+ public static void AddAuthorizationPolicySetup(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddAuthorization(options =>
+ {
+ //影像质控策略 只允许 IC IQC进行操作
+ options.AddPolicy(IRaCISPolicy.CRC_IQC, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.IQC).ToString());
+ });
+
+ //一致性核查策略 只允许 IC PM APM 进行操作
+ options.AddPolicy(IRaCISPolicy.PM_APM_CRC, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString());
+ });
+
+
+ options.AddPolicy(IRaCISPolicy.PM_APM, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString());
+ });
+
+ options.AddPolicy(IRaCISPolicy.PM_IQC, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.IQC).ToString());
+ });
+
+
+ options.AddPolicy(IRaCISPolicy.PM, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString());
+ });
+
+
+ options.AddPolicy(IRaCISPolicy.IQC, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.IQC).ToString());
+ });
+
+
+ options.AddPolicy(IRaCISPolicy.PM_APM_CRC_QC, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(),((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.IQC).ToString());
+ });
+
+ options.AddPolicy(IRaCISPolicy.SPM_CPM, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString());
+ });
+
+ options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(),((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString());
+ });
+
+ options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM_SMM_CMM, policyBuilder =>
+ {
+ policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(),
+ ((int)UserTypeEnum.CPM).ToString(), ((int)UserTypeEnum.SMM).ToString(), ((int)UserTypeEnum.CMM).ToString());
+ });
+
+
+
+
+ });
+ }
+
+
+
+
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/JWTAuthSetup.cs b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/JWTAuthSetup.cs
new file mode 100644
index 0000000..dbe605f
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_ServiceExtensions/Authorization/JWTAuthSetup.cs
@@ -0,0 +1,133 @@
+using IRaCIS.Core.Application.Auth;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Primitives;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IRaCIS.Core.API
+{
+ public static class JWTAuthSetup
+ {
+ public static void AddJWTAuthSetup(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.Configure(configuration.GetSection("JwtSetting"));
+
+ var jwtSetting = new JwtSetting();
+ configuration.Bind("JwtSetting", jwtSetting);
+
+ services
+ .AddAuthentication(o =>
+ {
+ o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ o.DefaultChallengeScheme = nameof(ApiResponseHandler);
+ o.DefaultForbidScheme = nameof(ApiResponseHandler);
+ })
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = jwtSetting.Issuer,
+ ValidAudience = jwtSetting.Audience,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
+ // 默认 300s
+ ClockSkew = TimeSpan.Zero
+ };
+
+
+
+ options.Events = new JwtBearerEvents
+ {
+ OnMessageReceived = (context) =>
+ {
+
+ if (context.Request.Query.TryGetValue("access_token", out StringValues values))
+ {
+ var queryToken = values.FirstOrDefault();
+
+ if (!String.IsNullOrWhiteSpace(queryToken))
+ {
+ context.Token = queryToken;
+
+ return Task.CompletedTask;
+ }
+ }
+
+
+ //仅仅是访问文件的时候才会去取token认证 前端对cookie设置了有效期
+
+ if (context.Request.Path.ToString().Contains("IRaCISData") || context.Request.Path.ToString().Contains("SystemData") )
+ {
+ var cookieToken = context.Request.Cookies["access_token"];
+
+ if (!String.IsNullOrWhiteSpace(cookieToken))
+ {
+ context.Token = cookieToken;
+ }
+
+ }
+ return Task.CompletedTask;
+
+ }
+ };
+
+ // OPTION 1: use `Invio.Extensions.Authentication.JwtBearer`
+
+ //options.AddQueryStringAuthentication();
+
+
+
+ // OPTION 2: do it manually
+
+ #region
+ //options.Events = new JwtBearerEvents
+ //{
+ // OnMessageReceived = (context) => {
+
+ // if (!context.Request.Query.TryGetValue("access_token", out StringValues values))
+ // {
+ // return Task.CompletedTask;
+ // }
+
+ // if (values.Count > 1)
+ // {
+ // context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ // context.Fail(
+ // "Only one 'access_token' query string parameter can be defined. " +
+ // $"However, {values.Count:N0} were included in the request."
+ // );
+
+ // return Task.CompletedTask;
+ // }
+
+ // var token = values.Single();
+
+ // if (String.IsNullOrWhiteSpace(token))
+ // {
+ // context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ // context.Fail(
+ // "The 'access_token' query string parameter was defined, " +
+ // "but a value to represent the token was not included."
+ // );
+
+ // return Task.CompletedTask;
+ // }
+
+ // context.Token = token;
+
+ // return Task.CompletedTask;
+ // }
+ //};
+ #endregion
+
+ })
+ .AddScheme(nameof(ApiResponseHandler), o => { });
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_ServiceExtensions/AutoMapperSetup.cs b/src/service/IRaCIS.Core.API/_ServiceExtensions/AutoMapperSetup.cs
new file mode 100644
index 0000000..5797d89
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_ServiceExtensions/AutoMapperSetup.cs
@@ -0,0 +1,36 @@
+using AutoMapper.EquivalencyExpression;
+using IRaCIS.Core.Application.Service;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace IRaCIS.Core.API
+{
+ public static class AutoMapperSetup
+ {
+ public static void AddAutoMapperSetup(this IServiceCollection services)
+ {
+
+ services.AddAutoMapper(automapper =>
+ {
+ //AutoMapper.Collection.EntityFrameworkCore
+ automapper.AddCollectionMappers();
+
+ #region 会使 IncludeMembers 失效 不能全局使用
+ //mapping an EntityFramework Core DbContext-object.
+ //automapper.UseEntityFrameworkCoreModel(services);
+
+
+ //automapper.ForAllMaps((a, b) => b.ForAllMembers(opt => opt.Condition((src, dest, srcMember, desMenber) =>
+ //{
+ // //// Can test When Guid? -> Guid if source is null will change to Guid.Empty
+ // //Console.WriteLine("srcMember:" + srcMember + "desMenber:" + desMenber);
+ // return srcMember != null && srcMember.ToString() != Guid.Empty.ToString();
+ // // not want to map a null Guid? value to db Guid value
+ //})));
+ #endregion
+
+ }, typeof(QCConfig).Assembly);
+
+
+ }
+ }
+}
diff --git a/src/service/IRaCIS.Core.API/_ServiceExtensions/AutofacModuleSetup.cs b/src/service/IRaCIS.Core.API/_ServiceExtensions/AutofacModuleSetup.cs
new file mode 100644
index 0000000..fd32159
--- /dev/null
+++ b/src/service/IRaCIS.Core.API/_ServiceExtensions/AutofacModuleSetup.cs
@@ -0,0 +1,85 @@
+using Autofac;
+using Autofac.Extras.DynamicProxy;
+using IRaCIS.Core.Application;
+using IRaCIS.Core.Application.AOP;
+using IRaCIS.Core.Application.BackGroundJob;
+using IRaCIS.Core.Infra.EFCore;
+using Microsoft.AspNetCore.Http;
+using Panda.DynamicWebApi;
+using System;
+using System.Linq;
+using System.Reflection;
+using IRaCIS.Core.Domain.Models;
+using IRaCIS.Core.Domain.Share;
+using MediatR;
+using IRaCIS.Application.Services;
+using IRaCIS.Application.Interfaces;
+using AutoMapper;
+
+namespace IRaCIS.Core.API
+{
+ // ReSharper disable once IdentifierTypo
+ public class AutofacModuleSetup : Autofac.Module
+ {
+ protected override void Load(ContainerBuilder containerBuilder)
+ {
+
+ #region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
+
+ containerBuilder.RegisterGeneric(typeof(Repository<>))
+ .As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
+
+ containerBuilder.RegisterType().As().InstancePerLifetimeScope();
+
+ //containerBuilder.RegisterType().As().InstancePerLifetimeScope();
+
+ //containerBuilder.RegisterGeneric(typeof(EFUnitOfWork<>))
+ // .As(typeof(IEFUnitOfWork<>)).InstancePerLifetimeScope();//注册仓储
+
+ #endregion
+
+ #region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
+
+ //获取所有控制器类型并使用属性注入
+ containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
+ .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
+ .PropertiesAutowired();
+
+
+
+ #endregion
+
+
+ //containerBuilder.RegisterType().As().PropertiesAutowired().InstancePerLifetimeScope();
+ //containerBuilder.RegisterType().As().InstancePerLifetimeScope();
+
+
+
+ Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "IRaCIS.Core.Application.dll");
+ containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
+ .PropertiesAutowired().AsImplementedInterfaces().EnableClassInterceptors();
+
+ //Assembly infrastructure = Assembly.Load("IRaCIS.Core.Infra.EFCore");
+ //containerBuilder.RegisterAssemblyTypes(infrastructure).AsImplementedInterfaces();
+
+ containerBuilder.RegisterType().As().SingleInstance();
+ containerBuilder.RegisterType().As().InstancePerLifetimeScope();
+
+
+ //containerBuilder.RegisterType