wanggang
1 year ago
commit
b1a69d50e7
273 changed files with 124178 additions and 0 deletions
@ -0,0 +1,536 @@ |
|||
root = true |
|||
|
|||
[*] |
|||
indent_style = space |
|||
indent_size = 2 |
|||
end_of_line = lf |
|||
charset = utf-8 |
|||
trim_trailing_whitespace = true |
|||
insert_final_newline = true |
|||
|
|||
[*.md] |
|||
trim_trailing_whitespace = false |
|||
|
|||
[*.cs] |
|||
indent_size = 4 |
|||
dotnet_sort_system_directives_first = true |
|||
|
|||
# Don't use this. qualifier |
|||
dotnet_style_qualification_for_field = true:suggestion |
|||
dotnet_style_qualification_for_property = true:suggestion |
|||
|
|||
# use int x = .. over Int32 |
|||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
|||
|
|||
# use int.MaxValue over Int32.MaxValue |
|||
dotnet_style_predefined_type_for_member_access = true:suggestion |
|||
|
|||
# Require var all the time. |
|||
csharp_style_var_for_built_in_types = true:suggestion |
|||
csharp_style_var_when_type_is_apparent = true:suggestion |
|||
csharp_style_var_elsewhere = true:suggestion |
|||
|
|||
# Disallow throw expressions. |
|||
csharp_style_throw_expression = false:suggestion |
|||
|
|||
# Newline settings |
|||
csharp_new_line_before_open_brace = all |
|||
csharp_new_line_before_else = true |
|||
csharp_new_line_before_catch = true |
|||
csharp_new_line_before_finally = true |
|||
csharp_new_line_before_members_in_object_initializers = true |
|||
csharp_new_line_before_members_in_anonymous_types = true |
|||
|
|||
# Namespace settings |
|||
csharp_style_namespace_declarations = file_scoped:silent |
|||
|
|||
# Brace settings |
|||
csharp_prefer_braces = true:silent# Prefer curly braces even for one line of code |
|||
|
|||
[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] |
|||
indent_size = 2 |
|||
|
|||
# Xml config files |
|||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] |
|||
indent_size = 2 |
|||
|
|||
[*.json] |
|||
indent_size = 2 |
|||
|
|||
[*.{ps1,psm1}] |
|||
indent_size = 4 |
|||
|
|||
[*.sh] |
|||
indent_size = 4 |
|||
end_of_line = lf |
|||
|
|||
[*.{razor,cshtml}] |
|||
charset = utf-8-bom |
|||
|
|||
[*.{cs,vb}] |
|||
|
|||
# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time |
|||
dotnet_diagnostic.SYSLIB1054.severity = warning |
|||
|
|||
# CA1018: Mark attributes with AttributeUsageAttribute |
|||
dotnet_diagnostic.CA1018.severity = warning |
|||
|
|||
# CA1047: Do not declare protected member in sealed type |
|||
dotnet_diagnostic.CA1047.severity = warning |
|||
|
|||
# CA1305: Specify IFormatProvider |
|||
dotnet_diagnostic.CA1305.severity = warning |
|||
|
|||
# CA1416: Validate platform compatibility |
|||
dotnet_diagnostic.CA1416.severity = warning |
|||
|
|||
# CA1507: Use nameof to express symbol names |
|||
dotnet_diagnostic.CA1507.severity = warning |
|||
|
|||
# CA1725: Parameter names should match base declaration |
|||
dotnet_diagnostic.CA1725.severity = suggestion |
|||
|
|||
# CA1802: Use literals where appropriate |
|||
dotnet_diagnostic.CA1802.severity = warning |
|||
|
|||
# CA1805: Do not initialize unnecessarily |
|||
dotnet_diagnostic.CA1805.severity = warning |
|||
|
|||
# CA1810: Do not initialize unnecessarily |
|||
dotnet_diagnostic.CA1810.severity = warning |
|||
|
|||
# CA1821: Remove empty Finalizers |
|||
dotnet_diagnostic.CA1821.severity = warning |
|||
|
|||
# CA1822: Make member static |
|||
dotnet_diagnostic.CA1822.severity = warning |
|||
dotnet_code_quality.CA1822.api_surface = private, internal |
|||
|
|||
# CA1823: Avoid unused private fields |
|||
dotnet_diagnostic.CA1823.severity = warning |
|||
|
|||
# CA1825: Avoid zero-length array allocations |
|||
dotnet_diagnostic.CA1825.severity = warning |
|||
|
|||
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly |
|||
dotnet_diagnostic.CA1826.severity = warning |
|||
|
|||
# CA1827: Do not use Count() or LongCount() when Any() can be used |
|||
dotnet_diagnostic.CA1827.severity = warning |
|||
|
|||
# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used |
|||
dotnet_diagnostic.CA1828.severity = warning |
|||
|
|||
# CA1829: Use Length/Count property instead of Count() when available |
|||
dotnet_diagnostic.CA1829.severity = warning |
|||
|
|||
# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder |
|||
dotnet_diagnostic.CA1830.severity = warning |
|||
|
|||
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate |
|||
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate |
|||
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate |
|||
dotnet_diagnostic.CA1831.severity = warning |
|||
dotnet_diagnostic.CA1832.severity = warning |
|||
dotnet_diagnostic.CA1833.severity = warning |
|||
|
|||
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable |
|||
dotnet_diagnostic.CA1834.severity = warning |
|||
|
|||
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' |
|||
dotnet_diagnostic.CA1835.severity = warning |
|||
|
|||
# CA1836: Prefer IsEmpty over Count |
|||
dotnet_diagnostic.CA1836.severity = warning |
|||
|
|||
# CA1837: Use 'Environment.ProcessId' |
|||
dotnet_diagnostic.CA1837.severity = warning |
|||
|
|||
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes |
|||
dotnet_diagnostic.CA1838.severity = warning |
|||
|
|||
# CA1839: Use 'Environment.ProcessPath' |
|||
dotnet_diagnostic.CA1839.severity = warning |
|||
|
|||
# CA1840: Use 'Environment.CurrentManagedThreadId' |
|||
dotnet_diagnostic.CA1840.severity = warning |
|||
|
|||
# CA1841: Prefer Dictionary.Contains methods |
|||
dotnet_diagnostic.CA1841.severity = warning |
|||
|
|||
# CA1842: Do not use 'WhenAll' with a single task |
|||
dotnet_diagnostic.CA1842.severity = warning |
|||
|
|||
# CA1843: Do not use 'WaitAll' with a single task |
|||
dotnet_diagnostic.CA1843.severity = warning |
|||
|
|||
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' |
|||
dotnet_diagnostic.CA1844.severity = warning |
|||
|
|||
# CA1845: Use span-based 'string.Concat' |
|||
dotnet_diagnostic.CA1845.severity = warning |
|||
|
|||
# CA1846: Prefer AsSpan over Substring |
|||
dotnet_diagnostic.CA1846.severity = warning |
|||
|
|||
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters |
|||
dotnet_diagnostic.CA1847.severity = warning |
|||
|
|||
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method |
|||
dotnet_diagnostic.CA1854.severity = warning |
|||
|
|||
# CA2007: Consider calling ConfigureAwait on the awaited task |
|||
dotnet_diagnostic.CA2007.severity = warning |
|||
|
|||
# CA2008: Do not create tasks without passing a TaskScheduler |
|||
dotnet_diagnostic.CA2008.severity = warning |
|||
|
|||
# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value |
|||
dotnet_diagnostic.CA2009.severity = warning |
|||
|
|||
# CA2011: Avoid infinite recursion |
|||
dotnet_diagnostic.CA2011.severity = warning |
|||
|
|||
# CA2012: Use ValueTask correctly |
|||
dotnet_diagnostic.CA2012.severity = warning |
|||
|
|||
# CA2013: Do not use ReferenceEquals with value types |
|||
dotnet_diagnostic.CA2013.severity = warning |
|||
|
|||
# CA2014: Do not use stackalloc in loops. |
|||
dotnet_diagnostic.CA2014.severity = warning |
|||
|
|||
# CA2016: Forward the 'CancellationToken' parameter to methods that take one |
|||
dotnet_diagnostic.CA2016.severity = warning |
|||
|
|||
# CA2200: Rethrow to preserve stack details |
|||
dotnet_diagnostic.CA2200.severity = warning |
|||
|
|||
# CA2208: Instantiate argument exceptions correctly |
|||
dotnet_diagnostic.CA2208.severity = warning |
|||
|
|||
# CA2245: Do not assign a property to itself |
|||
dotnet_diagnostic.CA2245.severity = warning |
|||
|
|||
# CA2246: Assigning symbol and its member in the same statement |
|||
dotnet_diagnostic.CA2246.severity = warning |
|||
|
|||
# CA2249: Use string.Contains instead of string.IndexOf to improve readability. |
|||
dotnet_diagnostic.CA2249.severity = warning |
|||
|
|||
# IDE0005: Remove unnecessary usings |
|||
dotnet_diagnostic.IDE0005.severity = warning |
|||
|
|||
# IDE0011: Curly braces to surround blocks of code |
|||
dotnet_diagnostic.IDE0011.severity = warning |
|||
|
|||
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) |
|||
dotnet_diagnostic.IDE0020.severity = warning |
|||
|
|||
# IDE0029: Use coalesce expression (non-nullable types) |
|||
dotnet_diagnostic.IDE0029.severity = warning |
|||
|
|||
# IDE0030: Use coalesce expression (nullable types) |
|||
dotnet_diagnostic.IDE0030.severity = warning |
|||
|
|||
# IDE0031: Use null propagation |
|||
dotnet_diagnostic.IDE0031.severity = warning |
|||
|
|||
# IDE0035: Remove unreachable code |
|||
dotnet_diagnostic.IDE0035.severity = warning |
|||
|
|||
# IDE0036: Order modifiers |
|||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion |
|||
dotnet_diagnostic.IDE0036.severity = warning |
|||
|
|||
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) |
|||
dotnet_diagnostic.IDE0038.severity = warning |
|||
|
|||
# IDE0043: Format string contains invalid placeholder |
|||
dotnet_diagnostic.IDE0043.severity = warning |
|||
|
|||
# IDE0044: Make field readonly |
|||
dotnet_diagnostic.IDE0044.severity = warning |
|||
|
|||
# IDE0051: Remove unused private members |
|||
dotnet_diagnostic.IDE0051.severity = warning |
|||
|
|||
# IDE0055: All formatting rules |
|||
dotnet_diagnostic.IDE0055.severity = suggestion |
|||
|
|||
# IDE0059: Unnecessary assignment to a value |
|||
dotnet_diagnostic.IDE0059.severity = warning |
|||
|
|||
# IDE0060: Remove unused parameter |
|||
dotnet_code_quality_unused_parameters = non_public:suggestion |
|||
dotnet_diagnostic.IDE0060.severity = warning |
|||
|
|||
# IDE0062: Make local function static |
|||
dotnet_diagnostic.IDE0062.severity = warning |
|||
|
|||
# IDE0161: Convert to file-scoped namespace |
|||
dotnet_diagnostic.IDE0161.severity = warning |
|||
|
|||
# IDE0200: Lambda expression can be removed |
|||
dotnet_diagnostic.IDE0200.severity = warning |
|||
|
|||
# IDE2000: Disallow multiple blank lines |
|||
dotnet_style_allow_multiple_blank_lines_experimental = false:silent |
|||
dotnet_diagnostic.IDE2000.severity = warning |
|||
|
|||
[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,scripts,stress}/**.cs}] |
|||
# CA1018: Mark attributes with AttributeUsageAttribute |
|||
dotnet_diagnostic.CA1018.severity = suggestion |
|||
# CA1507: Use nameof to express symbol names |
|||
dotnet_diagnostic.CA1507.severity = suggestion |
|||
# CA1802: Use literals where appropriate |
|||
dotnet_diagnostic.CA1802.severity = suggestion |
|||
# CA1805: Do not initialize unnecessarily |
|||
dotnet_diagnostic.CA1805.severity = suggestion |
|||
# CA1810: Do not initialize unnecessarily |
|||
dotnet_diagnostic.CA1810.severity = suggestion |
|||
# CA1822: Make member static |
|||
dotnet_diagnostic.CA1822.severity = suggestion |
|||
# CA1823: Avoid zero-length array allocations |
|||
dotnet_diagnostic.CA1825.severity = suggestion |
|||
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly |
|||
dotnet_diagnostic.CA1826.severity = suggestion |
|||
# CA1827: Do not use Count() or LongCount() when Any() can be used |
|||
dotnet_diagnostic.CA1827.severity = suggestion |
|||
# CA1829: Use Length/Count property instead of Count() when available |
|||
dotnet_diagnostic.CA1829.severity = suggestion |
|||
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable |
|||
dotnet_diagnostic.CA1834.severity = suggestion |
|||
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' |
|||
dotnet_diagnostic.CA1835.severity = suggestion |
|||
# CA1837: Use 'Environment.ProcessId' |
|||
dotnet_diagnostic.CA1837.severity = suggestion |
|||
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes |
|||
dotnet_diagnostic.CA1838.severity = suggestion |
|||
# CA1841: Prefer Dictionary.Contains methods |
|||
dotnet_diagnostic.CA1841.severity = suggestion |
|||
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' |
|||
dotnet_diagnostic.CA1844.severity = suggestion |
|||
# CA1845: Use span-based 'string.Concat' |
|||
dotnet_diagnostic.CA1845.severity = suggestion |
|||
# CA1846: Prefer AsSpan over Substring |
|||
dotnet_diagnostic.CA1846.severity = suggestion |
|||
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters |
|||
dotnet_diagnostic.CA1847.severity = suggestion |
|||
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method |
|||
dotnet_diagnostic.CA1854.severity = suggestion |
|||
# CA2007: Consider calling ConfigureAwait on the awaited task |
|||
dotnet_diagnostic.CA2007.severity = suggestion |
|||
# CA2008: Do not create tasks without passing a TaskScheduler |
|||
dotnet_diagnostic.CA2008.severity = suggestion |
|||
# CA2012: Use ValueTask correctly |
|||
dotnet_diagnostic.CA2012.severity = suggestion |
|||
# CA2249: Use string.Contains instead of string.IndexOf to improve readability. |
|||
dotnet_diagnostic.CA2249.severity = suggestion |
|||
# IDE0005: Remove unnecessary usings |
|||
dotnet_diagnostic.IDE0005.severity = suggestion |
|||
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) |
|||
dotnet_diagnostic.IDE0020.severity = suggestion |
|||
# IDE0029: Use coalesce expression (non-nullable types) |
|||
dotnet_diagnostic.IDE0029.severity = suggestion |
|||
# IDE0030: Use coalesce expression (nullable types) |
|||
dotnet_diagnostic.IDE0030.severity = suggestion |
|||
# IDE0031: Use null propagation |
|||
dotnet_diagnostic.IDE0031.severity = suggestion |
|||
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) |
|||
dotnet_diagnostic.IDE0038.severity = suggestion |
|||
# IDE0044: Make field readonly |
|||
dotnet_diagnostic.IDE0044.severity = suggestion |
|||
# IDE0051: Remove unused private members |
|||
dotnet_diagnostic.IDE0051.severity = suggestion |
|||
# IDE0059: Unnecessary assignment to a value |
|||
dotnet_diagnostic.IDE0059.severity = suggestion |
|||
# IDE0060: Remove unused parameters |
|||
dotnet_diagnostic.IDE0060.severity = suggestion |
|||
# IDE0062: Make local function static |
|||
dotnet_diagnostic.IDE0062.severity = suggestion |
|||
# IDE0200: Lambda expression can be removed |
|||
dotnet_diagnostic.IDE0200.severity = suggestion |
|||
|
|||
# CA2016: Forward the 'CancellationToken' parameter to methods that take one |
|||
dotnet_diagnostic.CA2016.severity = suggestion |
|||
|
|||
# Defaults for content in the shared src/ and shared runtime dir |
|||
|
|||
[{**/Shared/runtime/**.{cs,vb},src/Shared/test/Shared.Tests/runtime/**.{cs,vb},**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] |
|||
# CA1822: Make member static |
|||
dotnet_diagnostic.CA1822.severity = silent |
|||
# IDE0011: Use braces |
|||
dotnet_diagnostic.IDE0011.severity = silent |
|||
# IDE0055: Fix formatting |
|||
dotnet_diagnostic.IDE0055.severity = silent |
|||
# IDE0060: Remove unused parameters |
|||
dotnet_diagnostic.IDE0060.severity = silent |
|||
# IDE0062: Make local function static |
|||
dotnet_diagnostic.IDE0062.severity = silent |
|||
# IDE0161: Convert to file-scoped namespace |
|||
dotnet_diagnostic.IDE0161.severity = silent |
|||
|
|||
[{**/Shared/**.cs,**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] |
|||
# IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included. |
|||
dotnet_diagnostic.IDE0005.severity = silent |
|||
dotnet_style_qualification_for_field = true:suggestion |
|||
dotnet_style_qualification_for_property = true:suggestion |
|||
dotnet_style_qualification_for_method = false:suggestion |
|||
dotnet_style_qualification_for_event = false:suggestion |
|||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent |
|||
dotnet_style_readonly_field = true:suggestion |
|||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent |
|||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent |
|||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent |
|||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity: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:suggestion |
|||
dotnet_style_predefined_type_for_member_access = true:suggestion |
|||
|
|||
[*.cs] |
|||
#### 命名样式 #### |
|||
|
|||
# 命名规则 |
|||
|
|||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion |
|||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface |
|||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i |
|||
|
|||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion |
|||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types |
|||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case |
|||
|
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion |
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members |
|||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case |
|||
|
|||
# 符号规范 |
|||
|
|||
dotnet_naming_symbols.interface.applicable_kinds = interface |
|||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.interface.required_modifiers = |
|||
|
|||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum |
|||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.types.required_modifiers = |
|||
|
|||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method |
|||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |
|||
dotnet_naming_symbols.non_field_members.required_modifiers = |
|||
|
|||
# 命名样式 |
|||
|
|||
dotnet_naming_style.begins_with_i.required_prefix = I |
|||
dotnet_naming_style.begins_with_i.required_suffix = |
|||
dotnet_naming_style.begins_with_i.word_separator = |
|||
dotnet_naming_style.begins_with_i.capitalization = pascal_case |
|||
|
|||
dotnet_naming_style.pascal_case.required_prefix = |
|||
dotnet_naming_style.pascal_case.required_suffix = |
|||
dotnet_naming_style.pascal_case.word_separator = |
|||
dotnet_naming_style.pascal_case.capitalization = pascal_case |
|||
|
|||
dotnet_naming_style.pascal_case.required_prefix = |
|||
dotnet_naming_style.pascal_case.required_suffix = |
|||
dotnet_naming_style.pascal_case.word_separator = |
|||
dotnet_naming_style.pascal_case.capitalization = pascal_case |
|||
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_prefer_simple_using_statement = true:suggestion |
|||
csharp_style_prefer_method_group_conversion = true:silent |
|||
csharp_style_prefer_top_level_statements = true:silent |
|||
csharp_prefer_static_local_function = true:suggestion |
|||
csharp_style_prefer_readonly_struct = 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_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_prefer_utf8_string_literals = 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 |
@ -0,0 +1,3 @@ |
|||
* text=auto eol=lf |
|||
*.{cmd,[cC][mM][dD]} text eol=crlf |
|||
*.{bat,[bB][aA][tT]} text eol=crlf |
@ -0,0 +1,16 @@ |
|||
*.bak |
|||
|
|||
#fe |
|||
node_modules/ |
|||
dist/ |
|||
|
|||
#be |
|||
.vs/ |
|||
bin/ |
|||
obj/ |
|||
*.suo |
|||
*.user |
|||
*.db |
|||
*.db-shm |
|||
*.db-wal |
|||
|
@ -0,0 +1,35 @@ |
|||
{ |
|||
"version": "0.2.0", |
|||
"configurations": [ |
|||
{ |
|||
// Use IntelliSense to find out which attributes exist for C# debugging |
|||
// Use hover for the description of the existing attributes |
|||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md |
|||
"name": ".NET Core Launch (web)", |
|||
"type": "coreclr", |
|||
"request": "launch", |
|||
"preLaunchTask": "build", |
|||
// If you have changed target frameworks, make sure to update the program path. |
|||
"program": "${workspaceFolder}/src/WTA/bin/Debug/net7.0/WTA.dll", |
|||
"args": [], |
|||
"cwd": "${workspaceFolder}/src/WTA", |
|||
"stopAtEntry": false, |
|||
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser |
|||
"serverReadyAction": { |
|||
"action": "openExternally", |
|||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)" |
|||
}, |
|||
"env": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"sourceFileMap": { |
|||
"/Views": "${workspaceFolder}/Views" |
|||
} |
|||
}, |
|||
{ |
|||
"name": ".NET Core Attach", |
|||
"type": "coreclr", |
|||
"request": "attach" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,41 @@ |
|||
{ |
|||
"version": "2.0.0", |
|||
"tasks": [ |
|||
{ |
|||
"label": "build", |
|||
"command": "dotnet", |
|||
"type": "process", |
|||
"args": [ |
|||
"build", |
|||
"${workspaceFolder}/src/WTA/WTA.csproj", |
|||
"/property:GenerateFullPaths=true", |
|||
"/consoleloggerparameters:NoSummary" |
|||
], |
|||
"problemMatcher": "$msCompile" |
|||
}, |
|||
{ |
|||
"label": "publish", |
|||
"command": "dotnet", |
|||
"type": "process", |
|||
"args": [ |
|||
"publish", |
|||
"${workspaceFolder}/src/WTA/WTA.csproj", |
|||
"/property:GenerateFullPaths=true", |
|||
"/consoleloggerparameters:NoSummary" |
|||
], |
|||
"problemMatcher": "$msCompile" |
|||
}, |
|||
{ |
|||
"label": "watch", |
|||
"command": "dotnet", |
|||
"type": "process", |
|||
"args": [ |
|||
"watch", |
|||
"run", |
|||
"--project", |
|||
"${workspaceFolder}/src/WTA/WTA.csproj" |
|||
], |
|||
"problemMatcher": "$msCompile" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,48 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio Version 17 |
|||
VisualStudioVersion = 17.6.33717.318 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WTA", "src\WTA\WTA.csproj", "{FFACA971-25FC-4652-A510-8BFC76E42D87}" |
|||
EndProject |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WTA.Shared", "src\WTA.Shared\WTA.Shared.csproj", "{146358B1-1BD3-41B0-8D20-E613F422BA49}" |
|||
EndProject |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WTA.Infrastructure", "src\WTA.Infrastructure\WTA.Infrastructure.csproj", "{8A09D8E5-2DB8-4741-BFBF-0542968F4D26}" |
|||
EndProject |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WTA.Application", "src\WTA.Application\WTA.Application.csproj", "{82A8512D-456F-4849-BA9D-953B0C5E194A}" |
|||
EndProject |
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{F181D9F7-4185-4AE2-B32B-A57411029F69}" |
|||
ProjectSection(SolutionItems) = preProject |
|||
Directory.Packages.props = Directory.Packages.props |
|||
EndProjectSection |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|Any CPU = Debug|Any CPU |
|||
Release|Any CPU = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{FFACA971-25FC-4652-A510-8BFC76E42D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{FFACA971-25FC-4652-A510-8BFC76E42D87}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{FFACA971-25FC-4652-A510-8BFC76E42D87}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{FFACA971-25FC-4652-A510-8BFC76E42D87}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{146358B1-1BD3-41B0-8D20-E613F422BA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{146358B1-1BD3-41B0-8D20-E613F422BA49}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{146358B1-1BD3-41B0-8D20-E613F422BA49}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{146358B1-1BD3-41B0-8D20-E613F422BA49}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{8A09D8E5-2DB8-4741-BFBF-0542968F4D26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{8A09D8E5-2DB8-4741-BFBF-0542968F4D26}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{8A09D8E5-2DB8-4741-BFBF-0542968F4D26}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{8A09D8E5-2DB8-4741-BFBF-0542968F4D26}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{82A8512D-456F-4849-BA9D-953B0C5E194A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{82A8512D-456F-4849-BA9D-953B0C5E194A}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{82A8512D-456F-4849-BA9D-953B0C5E194A}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{82A8512D-456F-4849-BA9D-953B0C5E194A}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
GlobalSection(ExtensibilityGlobals) = postSolution |
|||
SolutionGuid = {53B37CFF-A4EE-43D2-97DE-F714E9F12F66} |
|||
EndGlobalSection |
|||
EndGlobal |
@ -0,0 +1,39 @@ |
|||
<Project> |
|||
<PropertyGroup> |
|||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageVersion Include="Autofac" Version="7.0.1" /> |
|||
<PackageVersion Include="Autofac.Configuration" Version="6.0.0" /> |
|||
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" /> |
|||
<PackageVersion Include="ClosedXML" Version="0.102.0" /> |
|||
<PackageVersion Include="Coravel" Version="4.2.1" /> |
|||
<PackageVersion Include="ExpressionTranslator" Version="2.5.0" /> |
|||
<PackageVersion Include="linq2db.EntityFrameworkCore" Version="7.5.0" /> |
|||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.8" /> |
|||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.8" /> |
|||
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.6.0" /> |
|||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="7.0.8" /> |
|||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.8" /> |
|||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.8" /> |
|||
<PackageVersion Include="Newtonsoft.Json.Schema" Version="3.0.15" /> |
|||
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" /> |
|||
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" /> |
|||
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.0" /> |
|||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" /> |
|||
<PackageVersion Include="Serilog.Sinks.Http" Version="8.0.0" /> |
|||
<PackageVersion Include="SkiaSharp" Version="2.88.3" /> |
|||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" /> |
|||
<PackageVersion Include="SkiaSharp.NativeAssets.Win32" Version="2.88.3" /> |
|||
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" /> |
|||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" /> |
|||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.31.0" /> |
|||
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.3.3" /> |
|||
<PackageVersion Include="System.Management" Version="7.0.2" /> |
|||
<PackageVersion Include="ValueInjecter" Version="3.2.0" /> |
|||
</ItemGroup> |
|||
</Project> |
@ -0,0 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<configuration> |
|||
<packageSources> |
|||
<clear /> |
|||
<add key="nuget.cdn.azure.cn" value="https://nuget.cdn.azure.cn/v3/index.json" protocolVersion="3" /> |
|||
</packageSources> |
|||
</configuration> |
@ -0,0 +1,37 @@ |
|||
# 说明 |
|||
|
|||
## 模块划分 |
|||
|
|||
1. 一个模块有多个数据库上下文 |
|||
1. 一个数据库上下文有多个实体配置和种子配置 |
|||
1. 每个实体配置和种子配置关联到一个数据上下文 |
|||
|
|||
## 权限自动生成 |
|||
|
|||
1. IResource 标记资源,IResourceService\<TResource\> where TResource:IResource 标记服务 |
|||
1. Entity 默认实现 IResource 接口 |
|||
1. 泛型控制器 GenericControlle 默认实现 Entity 的增删改查导入导出操作,其他自定义操作可以继承泛型控制器 |
|||
1. 非 Entity 资源可以手动实现 IResource 和 IResourceService 接口 |
|||
|
|||
## C# 到 JSON Schema 类型映射 |
|||
|
|||
### 值类型 |
|||
|
|||
1. bool?=>boolean[nullable] |
|||
1. int/long?=>integer[nullable] |
|||
1. float/double/decimal?=>number[nullable] |
|||
1. Guid/DateTime/Enum?=>string[nullable] |
|||
|
|||
### 引用类型 |
|||
|
|||
1. string=>string |
|||
1. object=>object |
|||
1. IEnumerable=>array |
|||
|
|||
## 格式化 |
|||
|
|||
format 用于格式验证 |
|||
|
|||
## 输入 |
|||
|
|||
input 用于输入控件 |
@ -0,0 +1,47 @@ |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.OutputCaching; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using WTA.Shared.Captcha; |
|||
|
|||
namespace WTA.Application.Captcha; |
|||
|
|||
[Route("api/[controller]")]
|
|||
public class CaptchaController : Controller |
|||
{ |
|||
private readonly IDistributedCache _cache; |
|||
private readonly ICaptchaService _captchaService; |
|||
|
|||
public CaptchaController(IDistributedCache cache, ICaptchaService captchaService) |
|||
{ |
|||
this._cache = cache; |
|||
this._captchaService = captchaService; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[AllowAnonymous] |
|||
[OutputCache(NoStore = true)] |
|||
public IActionResult Index() |
|||
{ |
|||
var code = string.Empty; |
|||
var builder = new StringBuilder(); |
|||
builder.Append(code); |
|||
for (var i = 0; i < 4; i++) |
|||
{ |
|||
var random = new byte[1]; |
|||
using var generator = RandomNumberGenerator.Create(); |
|||
generator.GetBytes(random); |
|||
builder.Append(new Random(Convert.ToInt32(random[0])).Next(0, 9)); |
|||
} |
|||
code = builder.ToString(); |
|||
var key = Guid.NewGuid().ToString(); |
|||
this._cache.SetString(key, code, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(5) }); |
|||
return Json(new |
|||
{ |
|||
Captcha = this._captchaService.Create(code), |
|||
CaptchaKey = key |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Shared.Controllers; |
|||
using WTA.Shared.Data; |
|||
|
|||
namespace WTA.Application.Identity.Controllers; |
|||
|
|||
public class ConnectionStringController : GenericController<ConnectionString, ConnectionString, ConnectionString, ConnectionString, ConnectionString, ConnectionString> |
|||
{ |
|||
public ConnectionStringController(ILogger<ConnectionString> logger, IRepository<ConnectionString> repository) : base(logger, repository) |
|||
{ |
|||
this.Repository.DisableTenantFilter(); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
using WTA.Shared.Controllers; |
|||
using WTA.Shared.Data; |
|||
|
|||
namespace WTA.Application.Identity.Controllers; |
|||
|
|||
public class PermissionController : GenericController<Permission, Permission, Permission, Permission, Permission, Permission> |
|||
{ |
|||
public PermissionController(ILogger<Permission> logger, IRepository<Permission> repository) : base(logger, repository) |
|||
{ |
|||
this.Repository.DisableTenantFilter(); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Shared.Controllers; |
|||
using WTA.Shared.Data; |
|||
|
|||
namespace WTA.Application.Identity.Controllers; |
|||
|
|||
public class TenantController : GenericController<Tenant, Tenant, Tenant, Tenant, Tenant, Tenant> |
|||
{ |
|||
public TenantController(ILogger<Tenant> logger, IRepository<Tenant> repository) : base(logger, repository) |
|||
{ |
|||
this.Repository.DisableTenantFilter(); |
|||
} |
|||
} |
@ -0,0 +1,208 @@ |
|||
using System.Globalization; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Application.Identity.Models; |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Authentication; |
|||
using WTA.Shared.Controllers; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Extensions; |
|||
using WTA.Shared.Identity; |
|||
|
|||
namespace WTA.Application.Identity.Controllers; |
|||
|
|||
[Route("api/{culture=zh}/[controller]")]
|
|||
[ApiExplorerSettings(GroupName = nameof(IdentityModule))] |
|||
public class TokenController : BaseController, IResourceService<Token> |
|||
{ |
|||
private readonly TokenValidationParameters _tokenValidationParameters; |
|||
private readonly IdentityOptions _identityOptions; |
|||
private readonly IPasswordHasher _passwordHasher; |
|||
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; |
|||
private readonly SigningCredentials _credentials; |
|||
private readonly IRepository<Tenant> _tenantRepository; |
|||
private readonly IRepository<User> _userRepository; |
|||
|
|||
public TokenController(TokenValidationParameters tokenValidationParameters, |
|||
JwtSecurityTokenHandler jwtSecurityTokenHandler, |
|||
SigningCredentials credentials, |
|||
IOptions<IdentityOptions> identityOptions, |
|||
IPasswordHasher passwordHasher, |
|||
IRepository<Tenant> _tenantRepository, |
|||
IRepository<User> userRepository) |
|||
{ |
|||
this._tokenValidationParameters = tokenValidationParameters; |
|||
this._jwtSecurityTokenHandler = jwtSecurityTokenHandler; |
|||
this._credentials = credentials; |
|||
this._identityOptions = identityOptions.Value; |
|||
this._passwordHasher = passwordHasher; |
|||
this._tenantRepository = _tenantRepository; |
|||
this._userRepository = userRepository; |
|||
this._userRepository.DisableTenantFilter(); |
|||
} |
|||
|
|||
[HttpGet("[action]")]
|
|||
[AllowAnonymous] |
|||
public object Create() |
|||
{ |
|||
return typeof(LoginRequestModel).GetViewModel(); |
|||
} |
|||
|
|||
[HttpPost("[action]")]
|
|||
[AllowAnonymous] |
|||
public IActionResult Create([FromBody] LoginRequestModel model) |
|||
{ |
|||
if (this.ModelState.IsValid) |
|||
{ |
|||
try |
|||
{ |
|||
var additionalClaims = new List<Claim>(); |
|||
if (model.TenantId != null) |
|||
{ |
|||
if (this._tenantRepository.AsNoTracking().Any(o => o.Id == model.TenantId)) |
|||
{ |
|||
additionalClaims.Add(new Claim(nameof(model.TenantId), model.TenantId?.ToString()!)); |
|||
} |
|||
else |
|||
{//租户不存在
|
|||
this.ModelState.AddModelError(nameof(LoginRequestModel.TenantId), "租户不存在"); |
|||
} |
|||
} |
|||
if (this.ModelState.IsValid) |
|||
{ |
|||
var userQuery = this._userRepository.Queryable(); |
|||
var user = userQuery.FirstOrDefault(o => o.UserName == model.UserName); |
|||
if (!this._identityOptions.SupportsUserLockout) |
|||
{//未启用登录锁定
|
|||
if (user == null || user.PasswordHash != this._passwordHasher.HashPassword(model.Password, user.SecurityStamp!)) |
|||
{ |
|||
this.ModelState.AddModelError("", "用户名或密码错误"); |
|||
} |
|||
} |
|||
else |
|||
{//启用登录锁定
|
|||
if (user != null) |
|||
{ |
|||
if (user.LockoutEnd.HasValue) |
|||
{//已锁定
|
|||
if (DateTime.UtcNow > user.LockoutEnd.Value) |
|||
{//超时则解锁
|
|||
user.AccessFailedCount = 0; |
|||
user.LockoutEnd = null; |
|||
this._userRepository.SaveChanges(); |
|||
} |
|||
else |
|||
{//未超时禁止登录
|
|||
var minutes = (user.LockoutEnd!.Value - DateTime.UtcNow).TotalMinutes.ToString("f0", CultureInfo.InvariantCulture); |
|||
this.ModelState.AddModelError(nameof(LoginRequestModel.UserName), $"用户已锁定,{minutes} 分钟后解除锁定"); |
|||
} |
|||
} |
|||
if (!user.LockoutEnd.HasValue) |
|||
{//未锁定
|
|||
if (user.PasswordHash != this._passwordHasher.HashPassword(model.Password, user.SecurityStamp!)) |
|||
{//密码不正确
|
|||
user.AccessFailedCount++; |
|||
if (user.AccessFailedCount >= this._identityOptions.MaxFailedAccessAttempts) |
|||
{//超过次数则锁定
|
|||
user.LockoutEnd = DateTime.UtcNow + this._identityOptions.DefaultLockoutTimeSpan; |
|||
var minutes = (user.LockoutEnd!.Value - DateTime.UtcNow).TotalMinutes.ToString("f0", CultureInfo.InvariantCulture); |
|||
this.ModelState.AddModelError(nameof(LoginRequestModel.UserName), $"用户已锁定,{minutes} 分钟后解除锁定"); |
|||
} |
|||
else |
|||
{//未超过次数提示剩余次数
|
|||
this.ModelState.AddModelError(nameof(LoginRequestModel.Password), $"密码错误,{this._identityOptions.MaxFailedAccessAttempts - user.AccessFailedCount}次失败后将锁定用户"); |
|||
} |
|||
this._userRepository.SaveChanges(); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.ModelState.AddModelError(nameof(LoginRequestModel.UserName), "用户不存在"); |
|||
} |
|||
} |
|||
if (this.ModelState.IsValid) |
|||
{//验证成功
|
|||
var roles = this._userRepository.AsNoTracking() |
|||
.Where(o => o.UserName == model.UserName) |
|||
.SelectMany(o => o.UserRoles) |
|||
.Select(o => o.Role.Name) |
|||
.ToList() |
|||
.Select(o => new Claim(this._tokenValidationParameters.RoleClaimType, o)); |
|||
additionalClaims.AddRange(roles); |
|||
var subject = CreateSubject(model.UserName, additionalClaims); |
|||
return Json(new LoginResponseModel |
|||
{ |
|||
AccessToken = CreateToken(subject, this._identityOptions.AccessTokenExpires), |
|||
RefreshToken = CreateToken(subject, model.RememberMe ? TimeSpan.FromDays(365) : this._identityOptions.RefreshTokenExpires), |
|||
ExpiresIn = (long)this._identityOptions.AccessTokenExpires.TotalSeconds |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Console.WriteLine(ex.ToString()); |
|||
return Problem(ex.ToString()); |
|||
} |
|||
} |
|||
return BadRequest(this.ModelState.ToErrors()); |
|||
} |
|||
|
|||
[HttpPost("[action]")]
|
|||
[AllowAnonymous] |
|||
public LoginResponseModel Refresh(string refreshToken) |
|||
{ |
|||
var validationResult = this._jwtSecurityTokenHandler.ValidateTokenAsync(refreshToken, this._tokenValidationParameters).Result; |
|||
if (!validationResult.IsValid) |
|||
{ |
|||
throw new Exception("RefreshToken验证失败", innerException: validationResult.Exception); |
|||
} |
|||
var subject = validationResult.ClaimsIdentity; |
|||
return new LoginResponseModel |
|||
{ |
|||
AccessToken = CreateToken(subject, this._identityOptions.AccessTokenExpires), |
|||
RefreshToken = CreateToken(subject, validationResult.SecurityToken.ValidTo.Subtract(validationResult.SecurityToken.ValidFrom)), |
|||
ExpiresIn = (long)this._identityOptions.AccessTokenExpires.TotalSeconds |
|||
}; |
|||
} |
|||
|
|||
private ClaimsIdentity CreateSubject(string userName, List<Claim> additionalClaims) |
|||
{ |
|||
var claims = new List<Claim>(additionalClaims) { new Claim(this._tokenValidationParameters.NameClaimType, userName) }; |
|||
var subject = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme); |
|||
return subject; |
|||
} |
|||
|
|||
private string CreateToken(ClaimsIdentity subject, TimeSpan timeout) |
|||
{ |
|||
var now = DateTime.UtcNow; |
|||
var tokenDescriptor = new SecurityTokenDescriptor() |
|||
{ |
|||
// 签发者
|
|||
Issuer = this._identityOptions.Issuer, |
|||
// 接收方
|
|||
Audience = this._identityOptions.Audience, |
|||
// 凭据
|
|||
SigningCredentials = _credentials, |
|||
// 声明
|
|||
Subject = subject, |
|||
// 签发时间
|
|||
IssuedAt = now, |
|||
// 生效时间
|
|||
NotBefore = now, |
|||
// UTC 过期时间
|
|||
Expires = now.Add(timeout), |
|||
}; |
|||
var securityToken = this._jwtSecurityTokenHandler.CreateJwtSecurityToken(tokenDescriptor); |
|||
var token = this._jwtSecurityTokenHandler.WriteToken(securityToken); |
|||
return token; |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Authentication; |
|||
using WTA.Shared.Controllers; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Application.Identity.Controllers; |
|||
|
|||
[Implement<IAuthenticationService>] |
|||
public class UserController : GenericController<User, User, User, User, User, User>, IAuthenticationService |
|||
{ |
|||
public UserController(ILogger<User> logger, IRepository<User> repository) : base(logger, repository) |
|||
{ |
|||
this.Repository.DisableTenantFilter(); |
|||
} |
|||
|
|||
[HttpPost, Hidden] |
|||
public AuthenticateResult Authenticate(string name, string operation) |
|||
{ |
|||
var query = this.Repository.AsNoTracking(); |
|||
var result = new AuthenticateResult |
|||
{ |
|||
Succeeded = query.Any(o => o.UserName == name && |
|||
o.UserRoles.Any(o => o.Role.RolePermissions.Any(o => o.Permission.Type == PermissionType.Operation && o.Permission.Number == operation))) |
|||
}; |
|||
if (result.Succeeded) |
|||
{ |
|||
var rolePermissions = query |
|||
.Where(o => o.UserName == name) |
|||
.SelectMany(o => o.UserRoles) |
|||
.Select(o => o.Role) |
|||
.SelectMany(o => o.RolePermissions) |
|||
.Where(o => o.Permission.Children.Any(p => p.Number == operation)) |
|||
.ToList(); |
|||
result.EnableColumnLimit = rolePermissions.Any(o => o.EnableColumnLimit); |
|||
if (result.EnableColumnLimit) |
|||
{ |
|||
result.Columns = rolePermissions.Where(o => o.EnableColumnLimit).SelectMany(o => o.Columns).Distinct().ToList(); |
|||
} |
|||
result.EnableRowLimit = rolePermissions.Any(o => o.EnableRowLimit); |
|||
if (result.EnableRowLimit) |
|||
{ |
|||
result.Rows = rolePermissions.Where(o => o.EnableColumnLimit).SelectMany(o => o.Rows).ToList(); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
[HttpPost, Hidden] |
|||
[Display(Name = "用户信息")] |
|||
public User? Info() |
|||
{ |
|||
var user = this.Repository |
|||
.AsNoTracking() |
|||
.Include(o => o.Department) |
|||
.Include(o => o.UserRoles) |
|||
.ThenInclude(o => o.Role) |
|||
.ThenInclude(o => o.RolePermissions) |
|||
.ThenInclude(o => o.Permission) |
|||
.FirstOrDefault(o => o.UserName == this.User.Identity!.Name); |
|||
if (user != null) |
|||
{ |
|||
user.SecurityStamp = string.Empty; |
|||
user.PasswordHash = string.Empty; |
|||
} |
|||
return user!; |
|||
} |
|||
} |
@ -0,0 +1,90 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Application.Monitor.Entities; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.Data.Config; |
|||
|
|||
public class IdentityConfiguration : IDbConfig<IdentityDbContext>, IEntityTypeConfiguration<Tenant>, |
|||
IEntityTypeConfiguration<ConnectionString>, |
|||
IEntityTypeConfiguration<Department>, |
|||
IEntityTypeConfiguration<User>, |
|||
IEntityTypeConfiguration<Role>, |
|||
IEntityTypeConfiguration<Permission>, |
|||
IEntityTypeConfiguration<Post>, |
|||
IEntityTypeConfiguration<UserRole>, |
|||
IEntityTypeConfiguration<RolePermission>, |
|||
IEntityTypeConfiguration<JobItem>, |
|||
IEntityTypeConfiguration<DictionaryItem>, |
|||
IEntityTypeConfiguration<UserLogin> |
|||
{ |
|||
public void Configure(EntityTypeBuilder<Tenant> builder) |
|||
{ |
|||
builder.HasIndex(o => o.Name).IsUnique(); |
|||
builder.HasIndex(o => o.Number).IsUnique(); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<ConnectionString> builder) |
|||
{ |
|||
builder.HasOne(o => o.Parent).WithMany(o => o.ConnectionStrings).HasForeignKey(o => o.ParentId).OnDelete(DeleteBehavior.Cascade); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<Department> builder) |
|||
{ |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<User> builder) |
|||
{ |
|||
builder.HasOne(o => o.Post).WithMany(o => o.Users).HasForeignKey(o => o.PostId).OnDelete(DeleteBehavior.SetNull); |
|||
builder.HasOne(o => o.Department).WithMany(o => o.Users).HasForeignKey(o => o.DepartmentId).OnDelete(DeleteBehavior.SetNull); |
|||
builder.HasAlternateKey(o => o.UserName); |
|||
builder.HasIndex(o => o.NormalizedUserName).IsUnique(); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<Role> builder) |
|||
{ |
|||
builder.HasAlternateKey(o => o.Number); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<Permission> builder) |
|||
{ |
|||
builder.Property(o => o.Type).IsRequired(); |
|||
builder.Property(o => o.Columns).HasConversion(p => p.ToJson(), p => p.FromJson<Dictionary<string, string>>()!); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<UserRole> builder) |
|||
{ |
|||
builder.HasOne(o => o.User).WithMany(o => o.UserRoles).HasForeignKey(o => o.UserId).OnDelete(DeleteBehavior.Cascade); |
|||
builder.HasOne(o => o.Role).WithMany(o => o.UserRoles).HasForeignKey(o => o.RoleId).OnDelete(DeleteBehavior.Cascade); |
|||
builder.HasAlternateKey(o => new { o.UserId, o.RoleId }); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<RolePermission> builder) |
|||
{ |
|||
builder.HasOne(o => o.Role).WithMany(o => o.RolePermissions).HasForeignKey(o => o.RoleId).OnDelete(DeleteBehavior.Cascade); |
|||
builder.HasOne(o => o.Permission).WithMany(o => o.RolePermissions).HasForeignKey(o => o.PermissionId).OnDelete(DeleteBehavior.Cascade); |
|||
builder.HasAlternateKey(o => new { o.RoleId, o.PermissionId }); |
|||
builder.Property(o => o.Columns).HasConversion(p => p.ToJson(), p => p.FromJson<List<string>>()!); |
|||
builder.Property(o => o.Rows).HasConversion(p => p.ToJson(), p => p.FromJson<List<string>>()!); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<JobItem> builder) |
|||
{ |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<Post> builder) |
|||
{ |
|||
builder.HasAlternateKey(o => o.Number); |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<DictionaryItem> builder) |
|||
{ |
|||
} |
|||
|
|||
public void Configure(EntityTypeBuilder<UserLogin> builder) |
|||
{ |
|||
builder.HasAlternateKey(o => o.ConnectionId); |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
[IgnoreMultiTenancy] |
|||
public class IdentityDbContext : BaseDbContext<IdentityDbContext> |
|||
{ |
|||
public IdentityDbContext(DbContextOptions<IdentityDbContext> options) : base(options) |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,257 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Reflection; |
|||
using LinqToDB.Extensions; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
using Microsoft.AspNetCore.Mvc.Infrastructure; |
|||
using Microsoft.AspNetCore.Mvc.Routing; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Application.Monitor.Entities; |
|||
using WTA.Shared; |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Extensions; |
|||
using WTA.Shared.Identity; |
|||
|
|||
namespace WTA.Application.Identity.Data; |
|||
|
|||
public class IdentityDbSeed : IDbSeed<IdentityDbContext> |
|||
{ |
|||
private readonly IPasswordHasher _passwordHasher; |
|||
private readonly IActionDescriptorCollectionProvider _actionProvider; |
|||
|
|||
public IdentityDbSeed(IPasswordHasher passwordHasher, IActionDescriptorCollectionProvider actionProvider) |
|||
{ |
|||
this._passwordHasher = passwordHasher; |
|||
this._actionProvider = actionProvider; |
|||
} |
|||
|
|||
public void Seed(IdentityDbContext context) |
|||
{ |
|||
//禁用多租户和软删除过滤器
|
|||
context.DisableTenantFilter = true; ; |
|||
context.DisableSoftDeleteFilter = true; |
|||
|
|||
// 定时器初始化
|
|||
context.Set<JobItem>().Add(new JobItem { Name = "客户端监测", Cron = "* * * * *", Service = "WTA.Application.Monitor.UserLoginSrevice" }); |
|||
|
|||
// 数据字典初始化
|
|||
InitDictionaries(context); |
|||
|
|||
//租户初始化
|
|||
var tenant = new Tenant |
|||
{ |
|||
Name = "默认", |
|||
Number = "default", |
|||
DataBaseCreated = false, |
|||
ConnectionStrings = new List<ConnectionString> |
|||
{ |
|||
new ConnectionString(){ Name="Identity",Value="Data Source=data2.db" } |
|||
} |
|||
}.SetIdBy(o => o.Number); |
|||
context.Set<Tenant>().Add(tenant); |
|||
var defaultTenantId = tenant.Id; |
|||
|
|||
// 权限初始化
|
|||
InitPermissions(context); |
|||
|
|||
//部门初始化
|
|||
context.Set<Department>().Add( |
|||
new Department |
|||
{ |
|||
Name = "企业", |
|||
Number = "Enterprise", |
|||
Children = new List<Department> |
|||
{ |
|||
new Department { |
|||
Name="总经办", |
|||
Number="Root", |
|||
Children=new List<Department> |
|||
{ |
|||
new Department |
|||
{ |
|||
Name="部门", |
|||
Number="Department" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}.UpdateId() |
|||
.UpdatePath()); |
|||
|
|||
// 角色初始化
|
|||
var superRole = new Role |
|||
{ |
|||
IsReadonly = true, |
|||
Name = "超级管理员角色", |
|||
Number = "super", |
|||
}.SetIdBy(o => o.Number); |
|||
context.Set<Permission>().ToList().ForEach(o => superRole.RolePermissions.Add(new RolePermission |
|||
{ |
|||
IsReadonly = true, |
|||
RoleId = superRole.Id, |
|||
PermissionId = o.Id, |
|||
EnableColumnLimit = false, |
|||
Columns = o.Columns.Keys.ToList(), |
|||
// EnableRowLimit = true,
|
|||
// RowLimit = "CreatorId = {User.Id}"
|
|||
}.SetIdBy(o => new { o.RoleId, o.PermissionId }))); |
|||
context.Set<Role>().Add(superRole); |
|||
context.SaveChanges(); |
|||
|
|||
// 用户初始化
|
|||
var superUser = new User |
|||
{ |
|||
IsReadonly = true, |
|||
UserName = "super", |
|||
NormalizedUserName = "super".Normalize(), |
|||
Name = "超级管理员", |
|||
SecurityStamp = "123456", |
|||
PasswordHash = this._passwordHasher.HashPassword("123456", "123456"), |
|||
Properties = new Dictionary<string, string> { { "key1", "value1" } } |
|||
}.SetIdBy(o => o.UserName); |
|||
superUser.UserRoles.Add(new UserRole { IsReadonly = true, UserId = superUser.Id, RoleId = superRole.Id }.SetIdBy(o => new { o.UserId, o.RoleId })); |
|||
context.Set<User>().Add(superUser); |
|||
} |
|||
|
|||
private void InitDictionaries(DbContext context) |
|||
{ |
|||
} |
|||
|
|||
private void InitPermissions(DbContext context) |
|||
{ |
|||
//var controllerFeature = new ControllerFeature();
|
|||
//this._partManager.PopulateFeature(controllerFeature);
|
|||
//var controllerTypeInfos = controllerFeature.Controllers;
|
|||
var actionDescriptors = this._actionProvider.ActionDescriptors.Items; |
|||
|
|||
context.Set<Permission>().Add(new Permission |
|||
{ |
|||
IsReadonly = true, |
|||
Type = PermissionType.Resource, |
|||
Name = "首页", |
|||
Number = "home", |
|||
Path = "home", |
|||
Method = "POST", |
|||
Component = "home", |
|||
Icon = "home", |
|||
Order = -3, |
|||
}.UpdateId().UpdatePath()); |
|||
|
|||
//context.Set<Permission>().Add(new Permission
|
|||
//{
|
|||
// IsExternal = true,
|
|||
// IsReadonly = true,
|
|||
// Type = PermissionType.Resource,
|
|||
// Name = "帮助",
|
|||
// Number = "help",
|
|||
// Path = "https://element-plus.org/",
|
|||
// Method = "GET",
|
|||
// Icon = "ep-link",
|
|||
// Order = 1000,
|
|||
//}.UpdateId().UpdatePath());
|
|||
|
|||
WebApp.Current.Assemblies.SelectMany(o => o.GetTypes()).Where(o => o.IsClass && !o.IsAbstract && o.IsAssignableTo(typeof(IResource))).ForEach(resourceType => |
|||
{ |
|||
// 获取列
|
|||
var columns = resourceType.GetProperties() |
|||
//.Where(o => o.PropertyType.IsValueType || o.PropertyType == typeof(string))
|
|||
.OrderBy(o => o.GetCustomAttribute<DisplayAttribute>()?.GetOrder()) |
|||
.ToDictionary(o => o.Name, o => o.GetDisplayName()); |
|||
// 创建资源权限
|
|||
var resourcePermission = new Permission |
|||
{ |
|||
IsReadonly = true, |
|||
Type = PermissionType.Resource, |
|||
Name = resourceType.GetDisplayName(), |
|||
Number = resourceType.Name, |
|||
Path = resourceType.Name.ToSlugify(), |
|||
Component = resourceType.GetCustomAttribute<ComponentAttribute>()?.Component, |
|||
IsHidden = resourceType.HasAttribute<HiddenAttribute>(), |
|||
Icon = resourceType.GetCustomAttribute<IconAttribute>()?.Icon ?? IconAttribute.File, |
|||
Order = resourceType.GetCustomAttribute<OrderAttribute>()?.Order ?? OrderAttribute.Default, |
|||
Columns = columns |
|||
}.UpdateId(); |
|||
// 创建按钮权限
|
|||
var resourceServiceType = typeof(IResourceService<>).MakeGenericType(resourceType); |
|||
actionDescriptors |
|||
.Select(o => o as ControllerActionDescriptor) |
|||
.Where(o => o != null && o.ControllerTypeInfo.AsType().IsAssignableTo(resourceServiceType)) |
|||
.ForEach(actionDescriptor => |
|||
{ |
|||
var operation = actionDescriptor?.ActionName!; |
|||
var methodInfo = actionDescriptor?.MethodInfo!; |
|||
var method = (methodInfo.GetCustomAttributes().FirstOrDefault(o => o.GetType().IsAssignableTo(typeof(HttpMethodAttribute))) |
|||
as HttpMethodAttribute)?.HttpMethods?.FirstOrDefault() ?? "POST"; |
|||
if (method != "GET") |
|||
{ |
|||
resourcePermission.Children.Add(new Permission |
|||
{ |
|||
IsReadonly = true, |
|||
Type = PermissionType.Operation, |
|||
Name = methodInfo.GetDisplayName(), |
|||
Number = $"{actionDescriptor?.ControllerName}.{operation}", |
|||
Path = $"{operation.TrimEnd("Async").ToSlugify()}", |
|||
IsHidden = methodInfo.GetCustomAttributes<HiddenAttribute>().Any(), |
|||
Method = method, |
|||
Icon = methodInfo.GetCustomAttribute<IconAttribute>()?.Icon ?? $"{operation.TrimEnd("Async").ToSlugify()}", |
|||
Order = methodInfo.GetCustomAttribute<OrderAttribute>()?.Order ?? OrderAttribute.Default, |
|||
HtmlClass = methodInfo.GetCustomAttribute<HtmlClassAttribute>()?.Class ?? HtmlClassAttribute.Default, |
|||
IsTop = methodInfo.GetCustomAttribute<MultipleAttribute>() != null |
|||
}.UpdateId()); |
|||
} |
|||
}); |
|||
// 实体分组
|
|||
var groupAttribute = resourceType.GetCustomAttributes().FirstOrDefault(o => o.GetType().IsAssignableTo(typeof(GroupAttribute))); |
|||
if (groupAttribute != null) |
|||
{ |
|||
var groupNumber = groupAttribute.GetType().Name; |
|||
var groupPermission = context.Set<Permission>().FirstOrDefault(o => o.Number == groupAttribute.GetType().Name); |
|||
groupPermission ??= new Permission |
|||
{ |
|||
Type = PermissionType.Group, |
|||
Name = groupAttribute.GetType().GetDisplayName(), |
|||
Number = groupNumber, |
|||
Path = $"{groupAttribute.GetType().Name.TrimEnd("Attribute").ToSlugify()}", |
|||
IsHidden = groupAttribute.GetType().HasAttribute<HiddenAttribute>(), |
|||
Icon = groupAttribute.GetType().GetCustomAttribute<IconAttribute>()?.Icon ?? IconAttribute.Folder, |
|||
Order = groupAttribute.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? OrderAttribute.Default |
|||
}.UpdateId(); |
|||
groupPermission.Children.Add(resourcePermission); |
|||
var moduleType = groupAttribute.GetType().GetCustomAttributes() |
|||
.Where(o => o.GetType().IsGenericType && o.GetType().GetGenericTypeDefinition() == typeof(ModuleAttribute<>)) |
|||
.Select(o => o as ITypeAttribute).Select(o => o?.Type).FirstOrDefault(); |
|||
if (moduleType != null) |
|||
{ |
|||
var modulePermission = context.Set<Permission>().FirstOrDefault(o => o.Number == moduleType.Name); |
|||
if (modulePermission == null) |
|||
{ |
|||
modulePermission = new Permission |
|||
{ |
|||
Type = PermissionType.Module, |
|||
Name = moduleType.GetDisplayName(), |
|||
Number = moduleType.Name, |
|||
Path = moduleType.Name.TrimEnd("Module").ToSlugify(), |
|||
IsHidden = moduleType.HasAttribute<HiddenAttribute>(), |
|||
Icon = moduleType.GetCustomAttribute<IconAttribute>()?.Icon ?? IconAttribute.Folder, |
|||
Order = moduleType.GetCustomAttribute<OrderAttribute>()?.Order ?? OrderAttribute.Default |
|||
}.UpdateId(); |
|||
context.Set<Permission>().Add(modulePermission.UpdatePath()); |
|||
} |
|||
modulePermission.Children.Add(groupPermission); |
|||
} |
|||
else |
|||
{ |
|||
context.Set<Permission>().AddOrUpdate(groupPermission.UpdatePath()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
context.Set<Permission>().Add(resourcePermission.UpdatePath()); |
|||
} |
|||
context.SaveChanges(); |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,642 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Domain; |
|||
using WTA.Shared.Module; |
|||
|
|||
namespace WTA.Application.Identity.Entities.BaseData; |
|||
|
|||
[Order(7)] |
|||
[SystemManagement] |
|||
[Display(Name = "物料主数据")] |
|||
public class Class1 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(8)] |
|||
[SystemManagement] |
|||
[Display(Name = "客户零件关系")] |
|||
public class Class2 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(9)] |
|||
[SystemManagement] |
|||
[Display(Name = "客户端替换件关系")] |
|||
public class Class3 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(10)] |
|||
[SystemManagement] |
|||
[Display(Name = "寄售库出库总成替换关系")] |
|||
public class Class4 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(11)] |
|||
[SystemManagement] |
|||
[Display(Name = "期间设置")] |
|||
public class Class5 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(12)] |
|||
[SystemManagement] |
|||
[Display(Name = "销售价格单")] |
|||
public class Class6 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Display(Name = "EDI业务")] |
|||
public class EdiAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[Edi] |
|||
[Display(Name = "EDI和HBPO核对")] |
|||
public class Class7 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Edi] |
|||
[Display(Name = "EDI和BBAC核对")] |
|||
public class Class8 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Display(Name = "JIS业务")] |
|||
public class JISModule : BaseModule |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[Module<JISModule>] |
|||
[Display(Name = "数据输入")] |
|||
public class JISDataInputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Module<JISModule>] |
|||
[Display(Name = "数据输出")] |
|||
public class JISDataOutputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[JISDataInput] |
|||
[Display(Name = "HBPO结算导入")] |
|||
public class Class9 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[JISDataInput] |
|||
[Display(Name = "BBAC结算导入")] |
|||
public class Class10 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[JISDataInput] |
|||
[Display(Name = "HBPO发运数据")] |
|||
public class Class11 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[JISDataInput] |
|||
[Display(Name = "BBAC发运数据")] |
|||
public class Class12 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[JISDataOutput] |
|||
[Display(Name = "HBPO结算核对明细输出")] |
|||
public class Class13 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[JISDataOutput] |
|||
[Display(Name = "BBAC结算核对明细输出")] |
|||
public class Class14 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[JISDataOutput] |
|||
[Display(Name = "HBPO无法出库明细与汇总输出")] |
|||
public class Class15 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[JISDataOutput] |
|||
[Display(Name = "BBAC无法出库明细与汇总输出")] |
|||
public class Class16 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(5)] |
|||
[JISDataOutput] |
|||
[Display(Name = "HBPO结算发货明细与汇总")] |
|||
public class Class17 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(6)] |
|||
[JISDataOutput] |
|||
[Display(Name = "BBAC结算发货明细与汇总")] |
|||
public class Class18 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
/////
|
|||
|
|||
[Order(3)] |
|||
[Display(Name = "JIT业务")] |
|||
public class JITModule : BaseModule |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[Module<JITModule>] |
|||
[Display(Name = "数据输入")] |
|||
public class JITDataInputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Module<JITModule>] |
|||
[Display(Name = "数据输出")] |
|||
public class JITDataOutputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[JITDataInput] |
|||
[Display(Name = "JIT件结算导入")] |
|||
public class Class19 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[JITDataInput] |
|||
[Display(Name = "JIT发运数据查询")] |
|||
public class Class20 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[JITDataOutput] |
|||
[Display(Name = "JIT件结算核对明细输出")] |
|||
public class Class21 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[JITDataOutput] |
|||
[Display(Name = "JIT件寄售库不能出库明细与汇总")] |
|||
public class Class22 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[JITDataOutput] |
|||
[Display(Name = "JIT件结算发货明细与汇总")] |
|||
public class Class23 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
/////
|
|||
|
|||
[Order(4)] |
|||
[Display(Name = "备件业务")] |
|||
public class BeiJianModule : BaseModule |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[Module<BeiJianModule>] |
|||
[Display(Name = "数据输入")] |
|||
public class BeiJianDataInputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Module<BeiJianModule>] |
|||
[Display(Name = "数据输出")] |
|||
public class BeiJianDataOutputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[BeiJianDataInput] |
|||
[Display(Name = "备件结算导入")] |
|||
public class Class24 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[BeiJianDataInput] |
|||
[Display(Name = "备件发运数据查询")] |
|||
public class Class25 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[BeiJianDataOutput] |
|||
[Display(Name = "备件结算核对明细输出")] |
|||
public class Class26 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[BeiJianDataOutput] |
|||
[Display(Name = "备件寄售库不能出库明细与汇总输出")] |
|||
public class Class27 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[BeiJianDataOutput] |
|||
[Display(Name = "备件有结算有发货明细与汇总输出")] |
|||
public class Class28 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[BeiJianDataOutput] |
|||
[Display(Name = "备件有结算无发货明细与汇总输出")] |
|||
public class Class29 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
/////
|
|||
|
|||
[Order(5)] |
|||
[Display(Name = "备件业务")] |
|||
public class MaiDanJianModule : BaseModule |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[Module<MaiDanJianModule>] |
|||
[Display(Name = "数据输入")] |
|||
public class MaiDanJianDataInputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[Module<MaiDanJianModule>] |
|||
[Display(Name = "数据输出")] |
|||
public class MaiDanJianDataOutputAttribute : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[MaiDanJianDataInput] |
|||
[Display(Name = "印度件结算导入")] |
|||
public class Class30 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[MaiDanJianDataInput] |
|||
[Display(Name = "印度件发运数据查询")] |
|||
public class Class31 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[MaiDanJianDataOutput] |
|||
[Display(Name = "印度件结算核对明细输出")] |
|||
public class Class32 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[MaiDanJianDataOutput] |
|||
[Display(Name = "印度件寄售库不能出库明细与汇总输出")] |
|||
public class Class33 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[MaiDanJianDataOutput] |
|||
[Display(Name = "印度件有结算有发货明细与汇总输出")] |
|||
public class Class34 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[MaiDanJianDataOutput] |
|||
[Display(Name = "印度件有结算无发货明细与汇总输出")] |
|||
public class Class35 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
//////
|
|||
[Order(6)] |
|||
[Display(Name = "出库单")] |
|||
public class ChuKuDanGroup : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "HBPO-JIS出库单")] |
|||
public class Class36 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "BBAC-JIS出库单")] |
|||
public class Class37 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "JIT件件出库单")] |
|||
public class Class38 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "备件出库单")] |
|||
public class Class39 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(5)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "印度件出库单")] |
|||
public class Class40 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(6)] |
|||
[ChuKuDanGroup] |
|||
[Display(Name = "不能出库记录出库业务")] |
|||
public class Class41 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
//////
|
|||
[Order(7)] |
|||
[Display(Name = "商务审核")] |
|||
public class ShangWuShenHeGroup : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[ShangWuShenHeGroup] |
|||
[Display(Name = "HBPO-JIS 商务待开票")] |
|||
public class Class42 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(2)] |
|||
[ShangWuShenHeGroup] |
|||
[Display(Name = "BBAC-JIS商务待开票")] |
|||
public class Class43 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(3)] |
|||
[ShangWuShenHeGroup] |
|||
[Display(Name = "JIT件商务发票待开票")] |
|||
public class Class44 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(4)] |
|||
[ShangWuShenHeGroup] |
|||
[Display(Name = "备件商务发票待开票")] |
|||
public class Class45 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
[Order(5)] |
|||
[ShangWuShenHeGroup] |
|||
[Display(Name = "印度件商务发票待开票")] |
|||
public class Class46 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
//////
|
|||
[Order(7)] |
|||
[Display(Name = "财务审核")] |
|||
public class CaiWuShenHeGroup : GroupAttribute |
|||
{ |
|||
} |
|||
|
|||
[Order(1)] |
|||
[CaiWuShenHeGroup] |
|||
[Display(Name = " BBAC-JIS财务管理审核")] |
|||
public class Class47 : BaseEntity |
|||
{ |
|||
} |
|||
|
|||
public class BaseDataDbConfig : IDbConfig<IdentityDbContext>, |
|||
IEntityTypeConfiguration<Class1>, |
|||
IEntityTypeConfiguration<Class2>, |
|||
IEntityTypeConfiguration<Class3>, |
|||
IEntityTypeConfiguration<Class4>, |
|||
IEntityTypeConfiguration<Class5>, |
|||
IEntityTypeConfiguration<Class6>, |
|||
IEntityTypeConfiguration<Class7>, |
|||
IEntityTypeConfiguration<Class8>, |
|||
IEntityTypeConfiguration<Class9>, |
|||
IEntityTypeConfiguration<Class10>, |
|||
IEntityTypeConfiguration<Class11>, |
|||
IEntityTypeConfiguration<Class12>, |
|||
IEntityTypeConfiguration<Class13>, |
|||
IEntityTypeConfiguration<Class14>, |
|||
IEntityTypeConfiguration<Class15>, |
|||
IEntityTypeConfiguration<Class16>, |
|||
IEntityTypeConfiguration<Class17>, |
|||
IEntityTypeConfiguration<Class18>, |
|||
IEntityTypeConfiguration<Class19>, |
|||
IEntityTypeConfiguration<Class20>, |
|||
IEntityTypeConfiguration<Class21>, |
|||
IEntityTypeConfiguration<Class22>, |
|||
IEntityTypeConfiguration<Class23>, |
|||
IEntityTypeConfiguration<Class24>, |
|||
IEntityTypeConfiguration<Class25>, |
|||
IEntityTypeConfiguration<Class26>, |
|||
IEntityTypeConfiguration<Class27>, |
|||
IEntityTypeConfiguration<Class28>, |
|||
IEntityTypeConfiguration<Class29>, |
|||
IEntityTypeConfiguration<Class30>, |
|||
IEntityTypeConfiguration<Class31>, |
|||
IEntityTypeConfiguration<Class32>, |
|||
IEntityTypeConfiguration<Class33>, |
|||
IEntityTypeConfiguration<Class34>, |
|||
IEntityTypeConfiguration<Class35>, |
|||
IEntityTypeConfiguration<Class36>, |
|||
IEntityTypeConfiguration<Class37>, |
|||
IEntityTypeConfiguration<Class38>, |
|||
IEntityTypeConfiguration<Class39>, |
|||
IEntityTypeConfiguration<Class40>, |
|||
IEntityTypeConfiguration<Class41>, |
|||
IEntityTypeConfiguration<Class42>, |
|||
IEntityTypeConfiguration<Class43>, |
|||
IEntityTypeConfiguration<Class44>, |
|||
IEntityTypeConfiguration<Class45>, |
|||
IEntityTypeConfiguration<Class46>, |
|||
IEntityTypeConfiguration<Class47> |
|||
{ |
|||
public void Configure(EntityTypeBuilder<Class1> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class2> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class3> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class4> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class5> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class6> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class7> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class8> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class9> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class10> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class11> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class12> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class13> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class14> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class15> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class16> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class17> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class18> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class19> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class20> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class21> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class22> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class23> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class24> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class25> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class26> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class27> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class28> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class29> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class30> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class31> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class32> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class33> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class34> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class35> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class36> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class37> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class38> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class39> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class40> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class41> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class42> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class43> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class44> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class45> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class46> builder) |
|||
{ } |
|||
|
|||
public void Configure(EntityTypeBuilder<Class47> builder) |
|||
{ } |
|||
} |
@ -0,0 +1,12 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Hidden] |
|||
[Order(4)] |
|||
[SystemManagement] |
|||
public class Department : BaseTreeEntity<Department> |
|||
{ |
|||
public List<User> Users { get; set; } = new List<User>(); |
|||
} |
@ -0,0 +1,10 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Order(6)] |
|||
[SystemManagement] |
|||
public class DictionaryItem : BaseTreeEntity<DictionaryItem> |
|||
{ |
|||
} |
@ -0,0 +1,32 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Order(3)] |
|||
[SystemManagement] |
|||
public class Permission : BaseTreeEntity<Permission> |
|||
{ |
|||
[Required] |
|||
public PermissionType? Type { get; set; } |
|||
|
|||
[Required] |
|||
public bool? IsHidden { get; set; } = false; |
|||
|
|||
[Required] |
|||
public bool? IsExternal { get; set; } = false; |
|||
|
|||
public string? Path { get; set; } |
|||
public string? Method { get; set; } |
|||
public string? Component { get; set; } |
|||
public string? Redirect { get; set; } |
|||
public string? Icon { get; set; } |
|||
public string? HtmlClass { get; set; } |
|||
|
|||
[Required] |
|||
public bool? IsTop { get; set; } = false; |
|||
|
|||
public Dictionary<string, string> Columns { get; set; } = new Dictionary<string, string>(); |
|||
public List<RolePermission> RolePermissions { get; set; } = new List<RolePermission>(); |
|||
} |
@ -0,0 +1,18 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
public enum PermissionType |
|||
{ |
|||
[Display(Name = "模块")] |
|||
Module = 10, |
|||
|
|||
[Display(Name = "分组")] |
|||
Group = 20, |
|||
|
|||
[Display(Name = "资源")] |
|||
Resource = 30, |
|||
|
|||
[Display(Name = "操作")] |
|||
Operation = 40 |
|||
} |
@ -0,0 +1,14 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Hidden] |
|||
[Order(5)] |
|||
[SystemManagement] |
|||
public class Post : BaseEntity |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public string Number { get; set; } = null!; |
|||
public List<User> Users { get; set; } = new List<User>(); |
|||
} |
@ -0,0 +1,14 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Order(2)] |
|||
[SystemManagement] |
|||
public class Role : BaseEntity |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public string Number { get; set; } = null!; |
|||
public List<UserRole> UserRoles { get; set; } = new List<UserRole>(); |
|||
public List<RolePermission> RolePermissions { get; set; } = new List<RolePermission>(); |
|||
} |
@ -0,0 +1,20 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Hidden] |
|||
[Display(Name = "角色权限")] |
|||
[SystemManagement] |
|||
public class RolePermission : BaseEntity |
|||
{ |
|||
public Guid RoleId { get; set; } |
|||
public Guid PermissionId { get; set; } |
|||
public Role Role { get; set; } = null!; |
|||
public Permission Permission { get; set; } = null!; |
|||
public bool EnableColumnLimit { get; internal set; } |
|||
public List<string> Columns { get; set; } = new List<string>(); |
|||
public bool EnableRowLimit { get; set; } |
|||
public List<string> Rows { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,9 @@ |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[SystemManagement, Hidden] |
|||
public class Token : IResource |
|||
{ |
|||
} |
@ -0,0 +1,36 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Order(1)] |
|||
[SystemManagement] |
|||
public class User : BaseEntity |
|||
{ |
|||
public string UserName { get; set; } = null!; |
|||
|
|||
[ScaffoldColumn(false)] |
|||
public string NormalizedUserName { get; set; } = null!; |
|||
|
|||
public string Name { get; set; } = null!; |
|||
|
|||
[ScaffoldColumn(false)] |
|||
public string SecurityStamp { get; set; } = null!; |
|||
|
|||
[ScaffoldColumn(false)] |
|||
public string PasswordHash { get; set; } = null!; |
|||
|
|||
public int AccessFailedCount { get; set; } |
|||
public DateTime? LockoutEnd { get; set; } |
|||
|
|||
[Navigation] |
|||
public Guid? DepartmentId { get; set; } |
|||
|
|||
public Guid? PostId { get; set; } |
|||
|
|||
public Department? Department { get; set; } |
|||
public Post? Post { get; set; } |
|||
|
|||
public List<UserRole> UserRoles { get; set; } = new List<UserRole>(); |
|||
} |
@ -0,0 +1,16 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
[Hidden] |
|||
[Display(Name = "用户角色")] |
|||
[SystemManagement] |
|||
public class UserRole : BaseEntity |
|||
{ |
|||
public Guid UserId { get; set; } |
|||
public Guid RoleId { get; set; } |
|||
public User User { get; set; } = null!; |
|||
public Role Role { get; set; } = null!; |
|||
} |
@ -0,0 +1,8 @@ |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Application.Identity.Entities; |
|||
|
|||
[Order(1)] |
|||
public class SystemManagementAttribute : GroupAttribute |
|||
{ |
|||
} |
@ -0,0 +1,17 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.Tenants; |
|||
|
|||
[Hidden] |
|||
[Tenants] |
|||
public class ConnectionString : BaseEntity |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public string Value { get; set; } = null!; |
|||
|
|||
[Navigation] |
|||
public Guid? ParentId { get; set; } |
|||
|
|||
public Tenant? Parent { get; set; } |
|||
} |
@ -0,0 +1,18 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Identity.Entities.Tenants; |
|||
|
|||
[Hidden] |
|||
[Tenants] |
|||
public class Tenant : BaseEntity |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public string Number { get; set; } = null!; |
|||
|
|||
[Required] |
|||
public bool? DataBaseCreated { get; set; } |
|||
|
|||
public List<ConnectionString> ConnectionStrings { get; set; } = new List<ConnectionString>(); |
|||
} |
@ -0,0 +1,8 @@ |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Application.Identity.Entities; |
|||
|
|||
//[Module<IdentityModule>]
|
|||
public class TenantsAttribute : GroupAttribute |
|||
{ |
|||
} |
@ -0,0 +1,16 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using WTA.Application.Monitor.Entities; |
|||
using WTA.Shared; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Module; |
|||
|
|||
namespace WTA.Application.Identity; |
|||
|
|||
[Order(1)] |
|||
public class IdentityModule : BaseModule |
|||
{ |
|||
public override void Configure(WebApplication app) |
|||
{ |
|||
WebApp.Current.UseScheduler<JobItem>(app); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace WTA.Application.Identity.Models; |
|||
|
|||
public class LoginRequestModel// : IValidatableObject
|
|||
{ |
|||
[ScaffoldColumn(false)] |
|||
[UIHint("select")] |
|||
public Guid? TenantId { get; set; } = null!; |
|||
|
|||
[MaxLength(64)] |
|||
public string UserName { get; set; } = null!; |
|||
|
|||
[MaxLength(64)] |
|||
[DataType(DataType.Password)] |
|||
public string Password { get; set; } = null!; |
|||
|
|||
//[ScaffoldColumn(false)]
|
|||
//public string CaptchaKey { get; set; } = null!;
|
|||
|
|||
//[UIHint("captcha")]
|
|||
//public string Captcha { get; set; } = null!;
|
|||
|
|||
public bool RememberMe { get; set; } |
|||
|
|||
//public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
|||
//{
|
|||
// using var scope = WebApp.Current.Services.CreateScope();
|
|||
// var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
|
|||
// var code = cache.GetString(this.CaptchaKey);
|
|||
// if (code == null)
|
|||
// {
|
|||
// yield return new ValidationResult("CaptchaExpired", new string[] { "Captcha" });
|
|||
// }
|
|||
// else if (code != this.Captcha)
|
|||
// {
|
|||
// yield return new ValidationResult("CaptchaError", new string[] { "Captcha" });
|
|||
// }
|
|||
//}
|
|||
} |
@ -0,0 +1,18 @@ |
|||
using System.Text.Json.Serialization; |
|||
|
|||
namespace WTA.Application.Identity.Models; |
|||
|
|||
public class LoginResponseModel |
|||
{ |
|||
[JsonPropertyName("token_type")] |
|||
public string TokenType = "Bearer"; |
|||
|
|||
[JsonPropertyName("access_token")] |
|||
public string? AccessToken { get; set; } |
|||
|
|||
[JsonPropertyName("refresh_token")] |
|||
public string? RefreshToken { get; set; } |
|||
|
|||
[JsonPropertyName("expires_in")] |
|||
public long? ExpiresIn { get; set; } |
|||
} |
@ -0,0 +1,9 @@ |
|||
using WTA.Application.Identity.Entities.SystemManagement; |
|||
|
|||
namespace WTA.Application.Identity.Models; |
|||
|
|||
public class UserInfoModel |
|||
{ |
|||
public User User { get; set; } = null!; |
|||
public List<Permission> Permissions { get; set; } = new List<Permission>(); |
|||
} |
@ -0,0 +1,35 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using WTA.Application.Identity.Entities.Tenants; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Tenants; |
|||
|
|||
namespace WTA.Application.Identity; |
|||
|
|||
[Implement<ITenantService>] |
|||
public class TenantService : ITenantService |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public string? TenantId { get; set; } |
|||
|
|||
public TenantService(IHttpContextAccessor httpContextAccessor, IServiceProvider serviceProvider) |
|||
{ |
|||
this._serviceProvider = serviceProvider; |
|||
this.TenantId = httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(o => o.Type == "TenantId")?.Value; |
|||
} |
|||
|
|||
public string? GetConnectionString(string connectionStringName) |
|||
{ |
|||
using var scope = this._serviceProvider.CreateScope(); |
|||
var repository = scope.ServiceProvider.GetRequiredService<IRepository<Tenant>>(); |
|||
repository.DisableTenantFilter(); |
|||
return repository |
|||
.AsNoTracking() |
|||
.Where(o => o.Number == this.TenantId) |
|||
.SelectMany(o => o.ConnectionStrings) |
|||
.FirstOrDefault(o => o.Name == connectionStringName) |
|||
?.Value; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Routing; |
|||
using Microsoft.Extensions.Localization; |
|||
using Microsoft.Extensions.Options; |
|||
using WTA.Shared.Controllers; |
|||
|
|||
namespace WTA.Application.Localization; |
|||
|
|||
[Route("api/[controller]")]
|
|||
public class LocalizationController : BaseController |
|||
{ |
|||
private readonly IStringLocalizer _localizer; |
|||
private readonly RequestLocalizationOptions _options; |
|||
|
|||
public LocalizationController(IOptions<RequestLocalizationOptions> options, IStringLocalizer localizer) |
|||
{ |
|||
this._options = options.Value; |
|||
this._localizer = localizer; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[AllowAnonymous] |
|||
public IActionResult Index(string? culture) |
|||
{ |
|||
if (culture != null) |
|||
{ |
|||
Thread.CurrentThread.CurrentCulture = this._options.SupportedCultures!.First(o => o.Name == culture); |
|||
} |
|||
var result = new |
|||
{ |
|||
Options = this._options.SupportedUICultures? |
|||
.Select(o => new { Value = o.Name, Label = o.NativeName }) |
|||
.ToList(), |
|||
Locale = Thread.CurrentThread.CurrentCulture.Name, |
|||
Messages = new Dictionary<string, object>(), |
|||
}; |
|||
foreach (var item in this._options.SupportedUICultures!) |
|||
{ |
|||
Thread.CurrentThread.CurrentCulture = item; |
|||
result.Messages.Add(item.Name, this._localizer.GetAllStrings().ToDictionary(o => o.Name, o => o.Value)); |
|||
} |
|||
return Json(result); |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Application.Monitor.Controllers; |
|||
|
|||
[Hidden] |
|||
[Order(3)] |
|||
[SystemMonitor] |
|||
[Component("monitor")] |
|||
public class Monitor : IResource |
|||
{ |
|||
} |
@ -0,0 +1,30 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Extensions; |
|||
using WTA.Shared.Monitor; |
|||
|
|||
namespace WTA.Application.Monitor.Controllers; |
|||
|
|||
[ApiExplorerSettings(GroupName = nameof(MonitorModule))] |
|||
[Route("api/{culture}/system-monitor/[controller]/[action]")]
|
|||
public class MonitorController : Controller, IResourceService<Monitor> |
|||
{ |
|||
private readonly IMonitorService _monitorService; |
|||
|
|||
public MonitorController(IMonitorService monitorService) |
|||
{ |
|||
this._monitorService = monitorService; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public IActionResult Index() |
|||
{ |
|||
return Json(typeof(MonitorModel).GetMetadataForType()); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public IActionResult Index(MonitorModel model) |
|||
{ |
|||
return Json(this._monitorService.GetStatus()); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Application.Monitor.Controllers; |
|||
|
|||
[Order(2)] |
|||
public class SystemMonitorAttribute : GroupAttribute |
|||
{ |
|||
} |
@ -0,0 +1,15 @@ |
|||
using WTA.Application.Monitor.Controllers; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Monitor.Entities; |
|||
|
|||
[Hidden] |
|||
[Order(2)] |
|||
[SystemMonitor] |
|||
public class JobItem : BaseEntity |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public string Cron { get; set; } = null!; |
|||
public string Service { get; set; } = null!; |
|||
} |
@ -0,0 +1,26 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using WTA.Application.Monitor.Controllers; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Application.Monitor.Entities; |
|||
|
|||
[Hidden] |
|||
[Order(1)] |
|||
[SystemMonitor] |
|||
public class UserLogin : BaseEntity |
|||
{ |
|||
public string ConnectionId { get; set; } = null!; |
|||
public string UserName { get; set; } = null!; |
|||
|
|||
[Required] |
|||
public DateTime? Login { get; set; } |
|||
|
|||
public DateTime? Logout { get; set; } |
|||
|
|||
[Required] |
|||
public bool? IsOnline { get; set; } |
|||
|
|||
public DateTime? Heartbeat { get; set; } |
|||
public string? UserAgent { get; set; } |
|||
} |
@ -0,0 +1,9 @@ |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Module; |
|||
|
|||
namespace WTA.Application.Monitor; |
|||
|
|||
[Order(100)] |
|||
public class MonitorModule : BaseModule |
|||
{ |
|||
} |
@ -0,0 +1,60 @@ |
|||
using WTA.Application.Monitor.Entities; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.EventBus; |
|||
using WTA.Shared.Job; |
|||
using WTA.Shared.Mappers; |
|||
using WTA.Shared.SignalR; |
|||
|
|||
namespace WTA.Application.Monitor; |
|||
|
|||
public class UserLoginSrevice : IEventHander<SignalRConnectedEvent>, |
|||
IEventHander<SignalRDisconnectedEvent>, |
|||
IEventHander<SignalRHeartbeatEvent>, |
|||
IEventHander<SignalCommandREvent>, |
|||
IJobService |
|||
{ |
|||
private readonly IRepository<UserLogin> _repository; |
|||
|
|||
public UserLoginSrevice(IRepository<UserLogin> repository) |
|||
{ |
|||
this._repository = repository; |
|||
} |
|||
|
|||
public Task Handle(SignalRConnectedEvent data) |
|||
{ |
|||
var entity = new UserLogin().FromModel(data); |
|||
entity.IsOnline = true; |
|||
this._repository.Insert(entity); |
|||
this._repository.SaveChanges(); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task Handle(SignalRDisconnectedEvent data) |
|||
{ |
|||
var entity = this._repository.Queryable().FirstOrDefault(o => o.ConnectionId == data.ConnectionId); |
|||
if (entity != null) |
|||
{ |
|||
entity.FromObject(data); |
|||
entity.IsOnline = false; |
|||
this._repository.SaveChanges(); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task Handle(SignalRHeartbeatEvent data) |
|||
{ |
|||
this._repository.Update(o => o.SetProperty(c => c.Heartbeat, DateTime.UtcNow), o => o.ConnectionId == data.ConnectionId); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task Handle(SignalCommandREvent data) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public void Invoke() |
|||
{ |
|||
var time = DateTime.UtcNow.AddMinutes(-1); |
|||
this._repository.Update(o => o.SetProperty(c => c.IsOnline, false).SetProperty(c => c.Logout, DateTime.UtcNow), o => o.Heartbeat == null || o.Heartbeat < time); |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\WTA.Shared\WTA.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,16 @@ |
|||
{ |
|||
"Application": "WTA Framework", |
|||
"Copyright": "all rights reserved © copyright", |
|||
"Test": "test", |
|||
"Login": "Login", |
|||
"Logout": "Logout", |
|||
"ConfirmLogout": "Confirm Logout?", |
|||
"Register": "Register", |
|||
"Tip": "Tip", |
|||
"Cancel": "Cancel", |
|||
"UserName": "User Name", |
|||
"Password": "Password", |
|||
"RememberMe": "Remember Me", |
|||
"ResetPassword": "Reset Password", |
|||
"UserCenter": "User Center" |
|||
} |
@ -0,0 +1,130 @@ |
|||
{ |
|||
"Application": "WTA开发框架", |
|||
"Copyright": "版权所有 © copyright ", |
|||
"Test": "测试", |
|||
"CompareAttribute": "{0}”和{1}不匹配", |
|||
"FileExtensionsAttribute": "{0}只接受一下扩展名的文件: {1}", |
|||
"MaxLengthAttribute": "{0}的最大长度为 {1}", |
|||
"MinLengthAttribute": "{0}的最小长度为 {1}", |
|||
"RangeAttribute": "{0}必需在 {1} 和 {2} 之间", |
|||
"RegularExpressionAttribute": "{0}”必需匹配{1}", |
|||
"RequiredAttribute": "{0}不能为空", |
|||
"StringLengthAttribute": "{0}的最大长度为 {1}", |
|||
"StringLengthAttributeIncludingMinimum": "{0}的长度在 {2} 和 {1} 之间", |
|||
"DataTypeAttribute_CreditCard": "{0}不是有效的信用卡号码", |
|||
"DataTypeAttribute_EmailAddress": "{0}不是有效的 Email 地址", |
|||
"DataTypeAttribute_PhoneNumber": "{0}不是有效的手机号码", |
|||
"DataTypeAttribute_Url": "{0}不是有效的 Url", |
|||
"DataTypeAttribute_Upload": "{0}的扩展名必须为:{1}", |
|||
"DataTypeAttribute_DateTime": "{0}不是有效的日期格式", |
|||
"CustomValidationAttribute": "{0}验证失败", |
|||
"ValidationAttribute": "{0}验证失败", |
|||
"True": "是", |
|||
"False": "否", |
|||
"Select": "选择", |
|||
"Confirm": "确定", |
|||
"Reset": "重置", |
|||
"RowIndex": "行号", |
|||
"Name": "名称", |
|||
"Number": "编号", |
|||
"Value": "值", |
|||
"Order": "序号", |
|||
"IsDisabled": "禁用", |
|||
"Properties": "属性", |
|||
"ParentId": "上级", |
|||
"LockoutEnabled": "启用锁定", |
|||
"LockoutEnd": "锁定截止", |
|||
"AccessFailedCount": "登录失败次数", |
|||
"IsSystem": "系统内置", |
|||
"IsReadonly": "只读", |
|||
"Audit": "审计", |
|||
"SelectAll": "全选", |
|||
"SelectInverse": "反选", |
|||
"Filter": "过滤", |
|||
"CreatedOn": "创建时间", |
|||
"CreatedBy": "创建人", |
|||
"UpdatedOn": "修改时间", |
|||
"UpdatedBy": "修改人", |
|||
"DeletedOn": "删除时间", |
|||
"DeletedBy": "删除人", |
|||
"ConcurrencyStamp": "并发戳", |
|||
"Operations": "操作", |
|||
"Disabled": "已禁用", |
|||
"DisplayOrder": "序号", |
|||
"IsDeleted": "已删除", |
|||
"Path": "路径", |
|||
"Method": "方法", |
|||
"IsTop": "顶部", |
|||
"HTMLClass": "class", |
|||
"InternalPath": "内部路径", |
|||
"Component": "组件", |
|||
"ServerTime": "服务器时间", |
|||
"OSArchitecture": "系统架构", |
|||
"OSDescription": "操作系统", |
|||
"ProcessArchitecture": "进程架构", |
|||
"Tip": "提示", |
|||
"Cancel": "操作取消", |
|||
"Index": "查询", |
|||
"Details": "详情", |
|||
"Create": "新建", |
|||
"Update": "更新", |
|||
"Import": "导入", |
|||
"Export": "导出", |
|||
"Remove": "移除", |
|||
"Restore": "还原", |
|||
"Delete": "删除", |
|||
"Authenticate": "验证", |
|||
"LoginModel": "登录", |
|||
"Login": "登录", |
|||
"Logout": "注销", |
|||
"ConfirmLogout": "确认退出?", |
|||
"Register": "注册", |
|||
"UserName": "用户名", |
|||
"Password": "密码", |
|||
"Email": "邮箱", |
|||
"EmailConfirmed": "邮箱已确认", |
|||
"RememberMe": "记住我", |
|||
"ResetPassword": "重置密码", |
|||
"UserCenter": "用户中心", |
|||
"Avatar": "头像", |
|||
"Tenant": "租户", |
|||
"ConnectionString": "连接字符串", |
|||
"TenantId": "租户", |
|||
"Tenants": "租户管理", |
|||
"Identity": "认证中心", |
|||
"SystemManagement": "基础数据", |
|||
"RoleId": "角色", |
|||
"PermissionId": "权限", |
|||
"UserRoles": "用户角色", |
|||
"EnableColumnLimit": "列权限", |
|||
"EnableRowLimit": "行权限", |
|||
"RolePermissions": "角色权限", |
|||
"DepartmentId": "部门", |
|||
"Cron": "定时器", |
|||
"Icon": "图标", |
|||
"Type": "类型", |
|||
"IsExternal": "外链", |
|||
"IsHidden": "隐藏", |
|||
"Redirect": "跳转", |
|||
"Columns": "列", |
|||
"IdentityModule": "系统管理", |
|||
"User": "用户", |
|||
"Role": "角色", |
|||
"Permission": "权限", |
|||
"Department": "部门", |
|||
"Post": "岗位", |
|||
"Dict": "字典", |
|||
"SystemMonitor": "系统监控", |
|||
"Monitor": "服务监控", |
|||
"MonitorModule": "系统监控", |
|||
"JobItem": "定时任务", |
|||
"Captcha": "验证码", |
|||
"CaptchaExpired": "验证码已过期", |
|||
"CaptchaError": "验证码错误", |
|||
"DictionaryItem": "数据字典", |
|||
"UserLogin": "登录历史", |
|||
"ConnectionId": "连接Id", |
|||
"IsOnline": "在线", |
|||
"Heartbeat": "心跳", |
|||
"UserAgent": "用户代理" |
|||
} |
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Resources\en.json" /> |
|||
<EmbeddedResource Include="Resources\zh.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\WTA.Application\WTA.Application.csproj" /> |
|||
<ProjectReference Include="..\WTA.Shared\WTA.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Application; |
|||
|
|||
public interface IExportModel<TEntity> where TEntity : class |
|||
{ |
|||
} |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Application; |
|||
|
|||
public interface IImportModel<TEntity> where TEntity : class |
|||
{ |
|||
} |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Application; |
|||
|
|||
public interface IResource |
|||
{ |
|||
} |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Application; |
|||
|
|||
public interface IResourceService<TResource> where TResource : IResource |
|||
{ |
|||
} |
@ -0,0 +1,22 @@ |
|||
using System.ComponentModel; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Shared.Application; |
|||
|
|||
public class PaginationModel<TSearchModel, TListModel> |
|||
{ |
|||
public PaginationModel() |
|||
{ |
|||
} |
|||
|
|||
[DefaultValue(1)] |
|||
public int PageIndex { get; set; } = 1; |
|||
|
|||
[DefaultValue(20)] |
|||
public int PageSize { get; set; } = 20; |
|||
|
|||
public string? OrderBy { get; set; } = $"{nameof(BaseEntity.Order)},{nameof(BaseEntity.CreatedOn)}"; |
|||
public int TotalCount { get; set; } |
|||
public List<TListModel> Items { get; set; } = new List<TListModel>(); |
|||
public TSearchModel Query { get; set; } = Activator.CreateInstance<TSearchModel>(); |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class AddOnlyAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class ComponentAttribute : Attribute |
|||
{ |
|||
public ComponentAttribute(string? component = null) |
|||
{ |
|||
this.Component = component; |
|||
} |
|||
|
|||
public string? Component { get; } |
|||
} |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
public class DbContextAttribute<T> : GenericAttribute<T> |
|||
{ |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class DisplayOnlyAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Field)] |
|||
public class ExpressionAttribute : Attribute |
|||
{ |
|||
public ExpressionAttribute(string expression) |
|||
{ |
|||
this.Expression = expression; |
|||
} |
|||
|
|||
public string Expression { get; } |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class GenericAttribute<T> : Attribute, ITypeAttribute |
|||
{ |
|||
public Type Type => typeof(T); |
|||
} |
|||
|
|||
public interface ITypeAttribute |
|||
{ |
|||
Type Type { get; } |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class GroupAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] |
|||
public class HiddenAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,13 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Method)] |
|||
public class HtmlClassAttribute : Attribute |
|||
{ |
|||
public HtmlClassAttribute(string @class) |
|||
{ |
|||
this.Class = @class; |
|||
} |
|||
|
|||
public static string Default { get; } = "el-button--primary"; |
|||
public string? Class { get; } = Default; |
|||
} |
@ -0,0 +1,14 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class IconAttribute : Attribute |
|||
{ |
|||
public IconAttribute(string icon) |
|||
{ |
|||
this.Icon = icon; |
|||
} |
|||
|
|||
public static string File { get; } = "file"; |
|||
public static string Folder { get; } = "folder"; |
|||
public string? Icon { get; } = File; |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class IgnoreMultiTenancyAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class IgnoreUpdateAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,26 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using WTA.Shared.DependencyInjection; |
|||
|
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] |
|||
public class ImplementAttribute<T> : Attribute, IImplementAttribute |
|||
{ |
|||
public ImplementAttribute(ServiceLifetime lifetime = ServiceLifetime.Transient, PlatformType platformType = PlatformType.All) |
|||
{ |
|||
this.ServiceType = typeof(T); |
|||
this.Lifetime = lifetime; |
|||
this.PlatformType = platformType; |
|||
} |
|||
|
|||
public ServiceLifetime Lifetime { get; set; } |
|||
public PlatformType PlatformType { get; set; } |
|||
public Type ServiceType { get; } |
|||
} |
|||
|
|||
public interface IImplementAttribute |
|||
{ |
|||
ServiceLifetime Lifetime { get; } |
|||
PlatformType PlatformType { get; } |
|||
Type ServiceType { get; } |
|||
} |
@ -0,0 +1,11 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class ImporterHeaderAttribute : Attribute |
|||
{ |
|||
public string Name { get; set; } = null!; |
|||
public bool IsIgnore { get; set; } |
|||
public bool IsAllowRepeat { get; set; } |
|||
public string ShowInputMessage { get; set; } = null!; |
|||
public string Format { get; set; } = null!; |
|||
} |
@ -0,0 +1,5 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
public class ModuleAttribute<T> : GenericAttribute<T> |
|||
{ |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Method)] |
|||
public class MultipleAttribute : Attribute |
|||
{ |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class NavigationAttribute : Attribute |
|||
{ |
|||
public NavigationAttribute(string? path = null) |
|||
{ |
|||
this.Path = path; |
|||
} |
|||
|
|||
public string? Path { get; } |
|||
} |
@ -0,0 +1,16 @@ |
|||
using WTA.Shared.Data; |
|||
|
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] |
|||
public class OperatorTypeAttribute : Attribute |
|||
{ |
|||
public OperatorTypeAttribute(OperatorType operatorType, string? propertyName = null) |
|||
{ |
|||
this.OperatorType = operatorType; |
|||
this.PropertyName = propertyName; |
|||
} |
|||
|
|||
public OperatorType OperatorType { get; } |
|||
public string? PropertyName { get; } |
|||
} |
@ -0,0 +1,12 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class OptionsAttribute : Attribute |
|||
{ |
|||
public OptionsAttribute(string? section = null) |
|||
{ |
|||
this.Section = section; |
|||
} |
|||
|
|||
public string? Section { get; } |
|||
} |
@ -0,0 +1,13 @@ |
|||
namespace WTA.Shared.Attributes; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] |
|||
public class OrderAttribute : Attribute |
|||
{ |
|||
public OrderAttribute(int order) |
|||
{ |
|||
this.Order = order; |
|||
} |
|||
|
|||
public static int Default { get; } |
|||
public int? Order { get; } = Default; |
|||
} |
@ -0,0 +1,11 @@ |
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public class AuthenticateResult |
|||
{ |
|||
public bool Succeeded { get; set; } |
|||
public bool Failed { get; set; } |
|||
public bool EnableColumnLimit { get; set; } |
|||
public bool EnableRowLimit { get; set; } |
|||
public List<string> Columns { get; set; } = new List<string>(); |
|||
public List<string> Rows { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,44 @@ |
|||
using System.Security.Claims; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public class CustomClaimsPrincipal : ClaimsPrincipal |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public CustomClaimsPrincipal(IServiceProvider serviceProvider, ClaimsPrincipal claimsPrincipal) : base(claimsPrincipal) |
|||
{ |
|||
this._serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public AuthenticateResult? Result { get; private set; } |
|||
|
|||
public override bool IsInRole(string role) |
|||
{ |
|||
var permissionService = this._serviceProvider.GetService<IAuthenticationService>(); |
|||
if (permissionService != null) |
|||
{ |
|||
// 优先使用本地验证
|
|||
this.Result = permissionService.Authenticate(this.Identity?.Name!, role); |
|||
} |
|||
else |
|||
{ |
|||
var configuration = this._serviceProvider.GetRequiredService<IConfiguration>(); |
|||
var authServer = configuration.GetValue<string>("AuthServer") ?? throw new ArgumentException($"AuthServer 未配置"); |
|||
var url = $"{authServer.TrimEnd('/')}/user/is-in-role"; |
|||
var httpClientFactory = this._serviceProvider.GetRequiredService<IHttpClientFactory>(); |
|||
var client = httpClientFactory.CreateClient(); |
|||
var data = new Dictionary<string, string> |
|||
{ |
|||
{ "name", this.Identity?.Name! }, |
|||
{ "role", role }, |
|||
}; |
|||
var response = client.PostAsync(url, new FormUrlEncodedContent(data)).Result; |
|||
this.Result = response.Content.ReadAsStringAsync().Result.FromJson<AuthenticateResult>()!; |
|||
} |
|||
return this.Result.Succeeded; |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public class CustomJwtBearerPostConfigureOptions : JwtBearerPostConfigureOptions, IPostConfigureOptions<JwtBearerOptions> |
|||
{ |
|||
private readonly CustomJwtSecurityTokenHandler _customJwtSecurityTokenHandler; |
|||
|
|||
public CustomJwtBearerPostConfigureOptions(CustomJwtSecurityTokenHandler customJwtSecurityTokenHandler) |
|||
{ |
|||
this._customJwtSecurityTokenHandler = customJwtSecurityTokenHandler; |
|||
} |
|||
|
|||
public new void PostConfigure(string? name, JwtBearerOptions options) |
|||
{ |
|||
options.SecurityTokenValidators.Clear(); |
|||
options.SecurityTokenValidators.Add(this._customJwtSecurityTokenHandler); |
|||
base.PostConfigure(name, options); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public class CustomJwtSecurityTokenHandler : JwtSecurityTokenHandler |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public CustomJwtSecurityTokenHandler(IServiceProvider serviceProvider) |
|||
{ |
|||
this._serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) |
|||
{ |
|||
return new CustomClaimsPrincipal(this._serviceProvider, base.ValidateToken(token, validationParameters, out validatedToken)); |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public interface IAuthenticationService |
|||
{ |
|||
AuthenticateResult Authenticate(string name, string operation); |
|||
} |
@ -0,0 +1,14 @@ |
|||
namespace WTA.Shared.Authentication; |
|||
|
|||
public class IdentityOptions |
|||
{ |
|||
public const string Position = "Identity"; |
|||
public string Issuer { get; set; } = "value"; |
|||
public string Audience { get; set; } = "value"; |
|||
public string Key { get; set; } = "0123456789abcdef0123456789abcdef"; |
|||
public TimeSpan AccessTokenExpires { get; set; } = TimeSpan.FromMinutes(10); |
|||
public TimeSpan RefreshTokenExpires { get; set; } = TimeSpan.FromDays(14); |
|||
public bool SupportsUserLockout { get; set; } = true; |
|||
public int MaxFailedAccessAttempts { get; set; } = 5; |
|||
public TimeSpan DefaultLockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(10); |
|||
} |
@ -0,0 +1,20 @@ |
|||
using SkiaSharp; |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Shared.Captcha; |
|||
|
|||
[Implement<ICaptchaService>] |
|||
public class CaptchaService : ICaptchaService |
|||
{ |
|||
public string Create(string code) |
|||
{ |
|||
using var image2d = new SKBitmap(120, 30, SKColorType.Bgra8888, SKAlphaType.Premul); |
|||
using var canvas = new SKCanvas(image2d); |
|||
using var paint = new SKPaint() { TextSize = 20, TextAlign = SKTextAlign.Center }; |
|||
canvas.DrawColor(SKColors.White); |
|||
canvas.DrawText(code, 15, 15, paint); |
|||
using var image = SKImage.FromBitmap(image2d); |
|||
using var data = image.Encode(SKEncodedImageFormat.Png, 100); |
|||
return $"data:image/png;base64,{Convert.ToBase64String(data.ToArray())}"; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
namespace WTA.Shared.Captcha; |
|||
|
|||
public interface ICaptchaService |
|||
{ |
|||
string Create(string code); |
|||
} |
@ -0,0 +1,44 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class BaseController : Controller |
|||
{ |
|||
public BaseController() |
|||
{ |
|||
} |
|||
|
|||
[ApiExplorerSettings(IgnoreApi = true)] |
|||
public override void OnActionExecuting(ActionExecutingContext context) |
|||
{ |
|||
var descriptor = (context.ActionDescriptor as ControllerActionDescriptor)!; |
|||
if (!descriptor.MethodInfo.CustomAttributes.Any(o => o.AttributeType == typeof(AllowAnonymousAttribute))) |
|||
{ |
|||
var operaation = $"{descriptor.ControllerName}.{descriptor.ActionName}"; |
|||
if (!this.HttpContext.User.Identity!.IsAuthenticated) |
|||
{ |
|||
context.Result = this.Unauthorized(); |
|||
} |
|||
else if (!context.HttpContext.User.IsInRole(operaation)) |
|||
{ |
|||
context.Result = this.Forbid(); |
|||
} |
|||
} |
|||
context.ModelState.Remove(nameof(BaseEntity.CreatedOn)); |
|||
context.ModelState.Remove(nameof(BaseEntity.ConcurrencyStamp)); |
|||
} |
|||
|
|||
[ApiExplorerSettings(IgnoreApi = true)] |
|||
public override void OnActionExecuted(ActionExecutedContext context) |
|||
{ |
|||
//(context.Result as ObjectResult)!.Value = new {
|
|||
// Items = new List<Dictionary<string, object>> { }
|
|||
//};
|
|||
//((context.Result as ObjectResult).Value.GetType().GetProperty("Items").GetValue((context.Result as ObjectResult).Value) as System.Collections.IList).Clear();
|
|||
base.OnActionExecuted(context); |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
using System.Reflection; |
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class ControllerModelConvention : IControllerModelConvention |
|||
{ |
|||
public void Apply(ControllerModel controller) |
|||
{ |
|||
if (controller.ControllerType.FullName!.StartsWith(WebApp.Current.Prefix)) |
|||
{ |
|||
if (controller.ApiExplorer.GroupName == null || controller.ControllerName == controller.ApiExplorer.GroupName) |
|||
{ |
|||
var types = controller.ControllerType.GetBaseClasses().Concat(new Type[] { controller.ControllerType }); |
|||
var genericControllerType = types.FirstOrDefault(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(GenericController<,,,,,>)); |
|||
if (genericControllerType != null) |
|||
{ |
|||
var entityType = genericControllerType.GetGenericArguments().FirstOrDefault(); |
|||
if (entityType != null) |
|||
{ |
|||
var groupAttribute = entityType.GetCustomAttributes().FirstOrDefault(o => o.GetType().IsAssignableTo(typeof(GroupAttribute))); |
|||
var moduleType = groupAttribute?.GetType().GetCustomAttributes() |
|||
.Where(o => o.GetType().IsGenericType && o.GetType().GetGenericTypeDefinition() == typeof(ModuleAttribute<>)) |
|||
.Select(o => o as ITypeAttribute).Select(o => o?.Type).FirstOrDefault(); |
|||
if (moduleType != null) |
|||
{ |
|||
controller.ApiExplorer.GroupName = moduleType?.Name; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,193 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Shared.Application; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Data; |
|||
using WTA.Shared.Domain; |
|||
using WTA.Shared.Extensions; |
|||
using WTA.Shared.Mappers; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
[GenericControllerNameConvention] |
|||
public class GenericController<TEntity, TModel, TListModel, TSearchModel, TImportModel, TExportModel> : BaseController, IResourceService<TEntity> |
|||
where TEntity : BaseEntity |
|||
where TModel : class |
|||
where TListModel : class |
|||
where TSearchModel : class |
|||
where TImportModel : class |
|||
where TExportModel : class |
|||
{ |
|||
public GenericController(ILogger<TEntity> logger, IRepository<TEntity> repository) |
|||
{ |
|||
this.Logger = logger; |
|||
this.Repository = repository; |
|||
} |
|||
|
|||
public ILogger<TEntity> Logger { get; } |
|||
public IRepository<TEntity> Repository { get; } |
|||
|
|||
[HttpGet] |
|||
public virtual IActionResult Index() |
|||
{ |
|||
return Json(typeof(PaginationModel<TSearchModel, TListModel>).GetViewModel()); |
|||
} |
|||
|
|||
[HttpPost, Multiple, Order(-4), HtmlClass("el-button--primary")] |
|||
public virtual IActionResult Index([FromBody] PaginationModel<TSearchModel, TListModel> model) |
|||
{ |
|||
var query = BuildQuery(model); |
|||
model.TotalCount = query.Count(); |
|||
if (!string.IsNullOrEmpty(model.OrderBy)) |
|||
{ |
|||
query = query.OrderBy(model.OrderBy); |
|||
} |
|||
query = query.Skip(model.PageSize * (model.PageIndex - 1)).Take(model.PageSize); |
|||
model.Items = query |
|||
.ToList() |
|||
.Select(o => o.ToObject<TListModel>()) |
|||
.ToList(); |
|||
return Json(model); |
|||
} |
|||
|
|||
protected virtual IQueryable<TEntity> BuildQuery(PaginationModel<TSearchModel, TListModel> model) |
|||
{ |
|||
var isTree = typeof(TEntity).IsAssignableTo(typeof(BaseTreeEntity<TEntity>)); |
|||
var query = this.Repository.AsNoTracking(); |
|||
query = query.Include(); |
|||
if (model.Query != null) |
|||
{ |
|||
query = query.Where(model: model.Query); |
|||
} |
|||
if (isTree) |
|||
{ |
|||
model.OrderBy ??= $"{nameof(BaseTreeEntity<TEntity>.ParentId)},{nameof(BaseEntity.Order)},{nameof(BaseEntity.CreatedOn)}"; |
|||
} |
|||
|
|||
return query; |
|||
} |
|||
|
|||
[HttpPost, Order(-2), HtmlClass("el-button--primary")] |
|||
public virtual IActionResult Details(Guid id) |
|||
{ |
|||
var entity = this.Repository.AsNoTracking().FirstOrDefault(o => o.Id == id); |
|||
var model = entity?.ToObject<TModel>(); |
|||
return Json(model); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public IActionResult Create() |
|||
{ |
|||
return Json(typeof(TModel).GetViewModel()); |
|||
} |
|||
|
|||
[HttpPost, Multiple, Order(-3), HtmlClass("el-button--success")] |
|||
public virtual IActionResult Create([FromBody] TModel model) |
|||
{ |
|||
if (this.ModelState.IsValid) |
|||
{ |
|||
try |
|||
{ |
|||
var entity = Activator.CreateInstance<TEntity>().FromModel(model); |
|||
this.Repository.Insert(entity); |
|||
this.Repository.SaveChanges(); |
|||
return NoContent(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Problem(ex.Message); |
|||
} |
|||
} |
|||
return Json(model); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual IActionResult Update(Guid id) |
|||
{ |
|||
return Json(new |
|||
{ |
|||
Schema = typeof(TModel).GetMetadataForType(), |
|||
Model = this.Repository.Queryable().FirstOrDefault(o => o.Id == id) |
|||
}); |
|||
} |
|||
|
|||
[HttpPost, Order(-1)] |
|||
public virtual IActionResult Update([FromBody] TModel model) |
|||
{ |
|||
if (this.ModelState.IsValid) |
|||
{ |
|||
try |
|||
{ |
|||
var id = model.GetPropertyValue<TModel, Guid>(nameof(BaseEntity.Id)); |
|||
var entity = this.Repository.Queryable().FirstOrDefault(o => o.Id == id); |
|||
if (entity == null) |
|||
{ |
|||
this.ModelState.AddModelError($"{nameof(id)}", $"not found entity by {id}"); |
|||
} |
|||
else |
|||
{ |
|||
entity.FromModel(model); |
|||
this.Repository.SaveChanges(); |
|||
return NoContent(); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Problem(ex.Message); |
|||
} |
|||
} |
|||
return Json(model); |
|||
} |
|||
|
|||
[HttpPost, Multiple, Order(0), HtmlClass("el-button--danger")] |
|||
public virtual IActionResult Delete([FromBody] Guid[] guids) |
|||
{ |
|||
try |
|||
{ |
|||
this.Repository.Delete(o=>guids.Contains(o.Id)); |
|||
this.Repository.SaveChanges(); |
|||
return NoContent(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Problem(ex.Message); |
|||
} |
|||
} |
|||
|
|||
[HttpPost, Multiple, Order(-2), HtmlClass("el-button--primary")] |
|||
public virtual IActionResult Import(IFormFile importexcelfile) |
|||
{ |
|||
try |
|||
{ |
|||
return NoContent(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Problem(ex.Message); |
|||
} |
|||
} |
|||
|
|||
[HttpPost, Multiple, Order(-1), HtmlClass("el-button--warning")] |
|||
public virtual IActionResult Export([FromBody] PaginationModel<TSearchModel, TListModel> model, bool includeAll = false, bool includeDeleted = false) |
|||
{ |
|||
try |
|||
{ |
|||
var query = this.BuildQuery(model); |
|||
if (!includeAll) |
|||
{ |
|||
query = query.Skip(model.PageSize * (model.PageIndex - 1)).Take(model.PageSize); |
|||
} |
|||
if (includeDeleted) |
|||
{ |
|||
this.Repository.DisableSoftDeleteFilter(); |
|||
} |
|||
return Json(query.ToList()); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Problem(ex.Message); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
using System.Reflection; |
|||
using Microsoft.AspNetCore.Mvc.ApplicationParts; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature> |
|||
{ |
|||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature) |
|||
{ |
|||
var typeInfos = WebApp.Current.Assemblies! |
|||
.SelectMany(o => o.GetTypes()) |
|||
.Where(o => !o.IsAbstract && o.IsAssignableTo(typeof(BaseEntity))) |
|||
.Select(o => o.GetTypeInfo()) |
|||
.ToList(); |
|||
foreach (var entityTypeInfo in typeInfos) |
|||
{ |
|||
var entityType = entityTypeInfo.AsType(); |
|||
if (!feature.Controllers.Any(o => o.Name == $"{entityType.Name}Controller")) |
|||
{ |
|||
var modelType = entityType; |
|||
var listType = entityType; |
|||
var searchType = entityType; |
|||
var importType = entityType; |
|||
var exportType = entityType; |
|||
var controllerType = typeof(GenericController<,,,,,>).MakeGenericType(entityType, modelType, listType, searchType, importType, exportType); |
|||
feature.Controllers.Add(controllerType.GetTypeInfo()); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] |
|||
public class GenericControllerNameConventionAttribute : Attribute, IControllerModelConvention |
|||
{ |
|||
public void Apply(ControllerModel controller) |
|||
{ |
|||
if (controller.ControllerType.IsGenericType) |
|||
{ |
|||
var controllerType = controller.ControllerType.IsGenericType ? controller.ControllerType : controller.ControllerType.BaseType!; |
|||
if (controllerType.GetGenericTypeDefinition() == typeof(GenericController<,,,,,>)) |
|||
{ |
|||
var entityType = controllerType.GenericTypeArguments[0]; |
|||
if (controller.ControllerName != entityType.Name) |
|||
{ |
|||
controller.ControllerName = entityType.Name!; |
|||
} |
|||
var moduleName = entityType.Assembly.GetName().Name; |
|||
if (string.IsNullOrEmpty(controller.ApiExplorer.GroupName) && !string.IsNullOrEmpty(moduleName)) |
|||
{ |
|||
controller.ApiExplorer.GroupName = moduleName; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
using System.Reflection; |
|||
using System.Text.RegularExpressions; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels; |
|||
using Microsoft.AspNetCore.Mvc.Routing; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class GenericControllerRouteConvention : IControllerModelConvention |
|||
{ |
|||
public void Apply(ControllerModel controller) |
|||
{ |
|||
var baseControllerType = controller.ControllerType.GetBaseClasses().Concat(new Type[] { controller.ControllerType }).FirstOrDefault(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(GenericController<,,,,,>)); |
|||
if (baseControllerType != null) |
|||
{ |
|||
var routeTemplate = $"api/{{culture=zh}}/"; |
|||
var genericType = baseControllerType.GenericTypeArguments[0]; |
|||
var groupAttribute = genericType.GetCustomAttributes().FirstOrDefault(o => o.GetType().IsAssignableTo(typeof(GroupAttribute))); |
|||
var moduleAttribute = groupAttribute?.GetType().GetCustomAttributes() |
|||
.Where(o => o.GetType().IsGenericType && o.GetType().GetGenericTypeDefinition() == typeof(ModuleAttribute<>)) |
|||
.Select(o => o as ITypeAttribute).Select(o => o?.Type).FirstOrDefault(); |
|||
if (moduleAttribute != null) |
|||
{ |
|||
routeTemplate += $"{moduleAttribute.Name.TrimEnd("Module").ToSlugify()}/"; |
|||
} |
|||
if (groupAttribute != null) |
|||
{ |
|||
routeTemplate += $"{groupAttribute.GetType().Name.TrimEnd("Attribute").ToSlugify()}/"; |
|||
} |
|||
routeTemplate += "[controller]/[action]"; |
|||
controller.Selectors.Add(new SelectorModel |
|||
{ |
|||
AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(routeTemplate)), |
|||
}); |
|||
controller.Actions.ForEach(action => |
|||
{ |
|||
if (!action.Attributes.Any(o => o.GetType().IsAssignableTo(typeof(HttpMethodAttribute)))) |
|||
{ |
|||
#pragma warning disable SYSLIB1045 // 转换为“GeneratedRegexAttribute”。
|
|||
var match = Regex.Match(action.ActionName, "^(Get|Post|Put|Delete|Patch|Head|Options)"); |
|||
#pragma warning restore SYSLIB1045 // 转换为“GeneratedRegexAttribute”。
|
|||
if (match.Success) |
|||
{ |
|||
var method = match.Groups[1].Value; |
|||
var actionName = action.ActionName.TrimStart(method); |
|||
(action.Attributes as List<object>)?.Add(new HttpMethodDefaultAttribute(new List<string> { method })); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
using Microsoft.AspNetCore.Mvc.Routing; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class HttpMethodDefaultAttribute : HttpMethodAttribute |
|||
{ |
|||
public HttpMethodDefaultAttribute(IEnumerable<string> httpMethods) : base(httpMethods) |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
using Microsoft.AspNetCore.Routing; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.Controllers; |
|||
|
|||
public class SlugifyParameterTransformer : IOutboundParameterTransformer |
|||
{ |
|||
public string? TransformOutbound(object? value) |
|||
{ |
|||
return value?.ToString()?.ToSlugify(); |
|||
} |
|||
} |
@ -0,0 +1,237 @@ |
|||
using System.Diagnostics; |
|||
using System.Reflection; |
|||
using Autofac; |
|||
using LinqToDB.EntityFrameworkCore; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.ChangeTracking; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using WTA.Shared.Attributes; |
|||
using WTA.Shared.Domain; |
|||
using WTA.Shared.Extensions; |
|||
using WTA.Shared.Tenants; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public abstract class BaseDbContext<T> : DbContext where T : DbContext |
|||
{ |
|||
public static readonly ILoggerFactory DefaultLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); }); |
|||
|
|||
public string? _tenantId; |
|||
|
|||
private static readonly ValueComparer<Dictionary<string, string>> DictionaryValueComparer = new( |
|||
(v1, v2) => v1 != null && v2 != null && v1.SequenceEqual(v2), o => o.GetHashCode()); |
|||
|
|||
private readonly string _tablePrefix; |
|||
|
|||
static BaseDbContext() |
|||
{ |
|||
LinqToDBForEFTools.Initialize(); |
|||
} |
|||
|
|||
public BaseDbContext(DbContextOptions<T> options) : base(options) |
|||
{ |
|||
this._tablePrefix = GetTablePrefix(); |
|||
this._tenantId = this.GetService<ITenantService>().TenantId; |
|||
} |
|||
|
|||
public bool DisableSoftDeleteFilter { get; set; } |
|||
public bool DisableTenantFilter { get; set; } |
|||
|
|||
public override int SaveChanges(bool acceptAllChangesOnSuccess) |
|||
{ |
|||
var entries = GetEntries(); |
|||
BeforeSave(entries); |
|||
return base.SaveChanges(acceptAllChangesOnSuccess); |
|||
} |
|||
|
|||
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) |
|||
{ |
|||
var entries = GetEntries(); |
|||
BeforeSave(entries); |
|||
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); |
|||
} |
|||
|
|||
protected virtual void BeforeSave(List<EntityEntry> entries) |
|||
{ |
|||
var userName = this.GetService<IHttpContextAccessor>().HttpContext?.User.Identity?.Name; |
|||
var tenant = this.GetService<ITenantService>().TenantId; |
|||
var now = DateTime.UtcNow; |
|||
foreach (var item in entries.Where(o => o.State == EntityState.Added || o.State == EntityState.Modified || o.State == EntityState.Deleted)) |
|||
{ |
|||
// 设置审计属性和租户
|
|||
if (item.Entity is BaseEntity entity) |
|||
{ |
|||
Debug.WriteLine($"{entity.Id},{entity.GetPropertyValue<BaseEntity, string>("Number")}"); |
|||
if (item.State == EntityState.Added) |
|||
{ |
|||
entity.CreatedOn = now; |
|||
entity.CreatedBy = userName ?? "super"; |
|||
entity.TenantId = tenant; |
|||
entity.IsDisabled ??= false; |
|||
entity.IsReadonly ??= false; |
|||
} |
|||
else if (item.State == EntityState.Modified) |
|||
{ |
|||
entity.UpdatedOn = now; |
|||
entity.UpdatedBy = userName; |
|||
} |
|||
else if (item.State == EntityState.Deleted) |
|||
{ |
|||
//if (entity is ISoftDeleteEntity)
|
|||
//{
|
|||
// throw new Exception("内置数据无法删除");
|
|||
//}
|
|||
if (entity.IsReadonly.HasValue && entity.IsReadonly.Value) |
|||
{ |
|||
throw new Exception("内置数据无法删除"); |
|||
} |
|||
} |
|||
entity.ConcurrencyStamp = Guid.NewGuid().ToString(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) |
|||
{ |
|||
optionsBuilder.UseLoggerFactory(DefaultLoggerFactory); |
|||
optionsBuilder.EnableSensitiveDataLogging(); |
|||
optionsBuilder.EnableDetailedErrors(); |
|||
} |
|||
|
|||
protected override void OnModelCreating(ModelBuilder modelBuilder) |
|||
{ |
|||
//默认配置
|
|||
WebApp.Current.DbContextTypes.GetValueOrDefault(GetType()) |
|||
?.ForEach(entityType => |
|||
{ |
|||
var entityTypeBuilder = modelBuilder.Entity(entityType); |
|||
//实体
|
|||
if (entityType.IsAssignableTo(typeof(BaseEntity))) |
|||
{ |
|||
//软删除、租户过滤
|
|||
this.GetType().GetMethod(nameof(this.CreateQueryFilter))?.MakeGenericMethod(entityType).Invoke(this, new object[] { modelBuilder }); |
|||
//
|
|||
//基类
|
|||
entityTypeBuilder.HasKey(nameof(BaseEntity.Id)); |
|||
entityTypeBuilder.Property(nameof(BaseEntity.Id)).ValueGeneratedNever(); |
|||
entityTypeBuilder.Property(nameof(BaseEntity.IsDisabled)).IsRequired(); |
|||
entityTypeBuilder.Property(nameof(BaseEntity.IsReadonly)).IsRequired(); |
|||
entityTypeBuilder.Property(nameof(BaseEntity.CreatedOn)).IsRequired(); |
|||
//行版本号
|
|||
entityTypeBuilder.Property(nameof(BaseEntity.ConcurrencyStamp)).ValueGeneratedNever(); |
|||
//扩展属性
|
|||
entityTypeBuilder.Property<Dictionary<string, string>>(nameof(BaseEntity.Properties)). |
|||
HasConversion(v => v.ToJson(), v => v.FromJson<Dictionary<string, string>>()!, DictionaryValueComparer); |
|||
//表名
|
|||
entityTypeBuilder.ToTable($"{this._tablePrefix}{entityTypeBuilder.Metadata.GetTableName()}"); |
|||
//属性
|
|||
entityTypeBuilder.Metadata.GetProperties().ForEach(prop => |
|||
{ |
|||
if (prop.PropertyInfo != null) |
|||
{ |
|||
//列注释
|
|||
entityTypeBuilder.Property(prop.Name).HasComment(prop.PropertyInfo?.GetDisplayName()); |
|||
if (prop.PropertyInfo!.PropertyType.GetUnderlyingType() == typeof(DateTime)) |
|||
{ |
|||
//EF 默认使用 DateTimeKind.Unspecified 读取,数据库应存储 UTC 格式,客户端根据所在时区进行展示
|
|||
if (prop.PropertyInfo!.PropertyType.IsNullableType()) |
|||
{ |
|||
// HasConversion(toDBValue,fromDBValue)
|
|||
entityTypeBuilder.Property<DateTime?>(prop.Name) |
|||
.HasConversion(v => v.HasValue ? (v.Value.Kind == DateTimeKind.Utc ? v : v.Value.ToUniversalTime()) : null, |
|||
v => v == null ? null : DateTime.SpecifyKind(v.Value, DateTimeKind.Utc)); |
|||
} |
|||
else |
|||
{ |
|||
// HasConversion(toDBValue,fromDBValue)
|
|||
entityTypeBuilder.Property<DateTime>(prop.Name) |
|||
.HasConversion(v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); |
|||
} |
|||
} |
|||
if (prop.PropertyInfo!.PropertyType.GetUnderlyingType().IsEnum) |
|||
{ |
|||
//枚举存为字符串
|
|||
entityTypeBuilder.Property(prop.Name).HasConversion<string>(); |
|||
} |
|||
} |
|||
}); |
|||
//TreeEntity
|
|||
if (entityType.IsAssignableTo(typeof(BaseTreeEntity<>).MakeGenericType(entityType))) |
|||
{ |
|||
entityTypeBuilder.HasOne(nameof(BaseTreeEntity<BaseEntity>.Parent)) |
|||
.WithMany(nameof(BaseTreeEntity<BaseEntity>.Children)) |
|||
.HasForeignKey(new string[] { nameof(BaseTreeEntity<BaseEntity>.ParentId) }) |
|||
.OnDelete(DeleteBehavior.NoAction); |
|||
entityTypeBuilder.Property(nameof(BaseTreeEntity<BaseEntity>.Name)).IsRequired(); |
|||
entityTypeBuilder.Property(nameof(BaseTreeEntity<BaseEntity>.Number)).IsRequired().HasMaxLength(64); |
|||
entityTypeBuilder.HasIndex(nameof(BaseTreeEntity<BaseEntity>.Number)).IsUnique(); |
|||
} |
|||
} |
|||
else if (entityType.IsAssignableTo(typeof(BaseViewEntity))) |
|||
{ |
|||
//视图
|
|||
entityTypeBuilder.HasNoKey().ToView($"{this._tablePrefix}{entityType.Name}"); |
|||
} |
|||
}); |
|||
|
|||
//自定义配置
|
|||
var applyEntityConfigurationMethod = typeof(ModelBuilder) |
|||
.GetMethods() |
|||
.Single( |
|||
e => e.Name == nameof(ModelBuilder.ApplyConfiguration) |
|||
&& e.ContainsGenericParameters |
|||
&& e.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() |
|||
== typeof(IEntityTypeConfiguration<>)); |
|||
if (WebApp.Current.DbConfigTypes.TryGetValue(GetType(), out var configTypes)) |
|||
{ |
|||
configTypes.ForEach(configType => |
|||
{ |
|||
var interfaces = configType.GetInterfaces() |
|||
.Where(type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) |
|||
.ToList(); |
|||
foreach (var item in interfaces) |
|||
{ |
|||
var entityType = item.GetGenericArguments()[0]; |
|||
var entityTypeBuilder = modelBuilder.GetType().GetMethods() |
|||
.FirstOrDefault(o => o.Name == "Entity" && o.IsGenericMethod)? |
|||
.MakeGenericMethod(new Type[] { entityType }) |
|||
.Invoke(modelBuilder, Array.Empty<object>()); |
|||
applyEntityConfigurationMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, new[] { Activator.CreateInstance(configType) }); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public void CreateQueryFilter<TEntity>(ModelBuilder builder) where TEntity : BaseEntity |
|||
{ |
|||
builder.Entity<TEntity>().HasQueryFilter(o => |
|||
(this.DisableSoftDeleteFilter == true || !o.IsDeleted) && |
|||
(this.DisableTenantFilter == true || o.TenantId == this._tenantId)); |
|||
} |
|||
|
|||
private List<EntityEntry> GetEntries() |
|||
{ |
|||
this.ChangeTracker.DetectChanges(); |
|||
var entries = this.ChangeTracker.Entries().ToList(); |
|||
return entries; |
|||
} |
|||
|
|||
private string GetTablePrefix() |
|||
{ |
|||
var prefix = this.GetType().GetCustomAttributes() |
|||
.Where(o => o.GetType().IsGenericType && o.GetType().GetGenericTypeDefinition() == typeof(ModuleAttribute<>)) |
|||
.Select(o => o as ITypeAttribute) |
|||
.Select(o => o!.Type.Name) |
|||
.FirstOrDefault()? |
|||
.TrimEnd("Module"); |
|||
|
|||
if (!string.IsNullOrEmpty(prefix)) |
|||
{ |
|||
prefix = $"{prefix}_"; |
|||
} |
|||
return prefix ?? ""; |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
using System.Linq.Expressions; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Query; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public class EfRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity |
|||
{ |
|||
private readonly DbContext _dbContext; |
|||
|
|||
public EfRepository(IServiceProvider serviceProvider) |
|||
{ |
|||
var dbContextType = WebApp.Current.DbContextTypes.FirstOrDefault(o => o.Value.Contains(typeof(TEntity))).Key; |
|||
var scope = serviceProvider.CreateScope(); |
|||
this._dbContext = (scope.ServiceProvider.GetRequiredService(dbContextType!) as DbContext)!; |
|||
} |
|||
|
|||
public IQueryable<TEntity> Queryable() |
|||
{ |
|||
return this._dbContext.Set<TEntity>(); |
|||
} |
|||
|
|||
public IQueryable<TEntity> AsNoTracking() |
|||
{ |
|||
return this._dbContext.Set<TEntity>().AsNoTracking(); |
|||
} |
|||
|
|||
public IQueryable<TEntity> AsNoTrackingWithIdentityResolution() |
|||
{ |
|||
return this._dbContext.Set<TEntity>().AsNoTrackingWithIdentityResolution(); |
|||
} |
|||
|
|||
public void Update(Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls, Expression<Func<TEntity, bool>> predicate) |
|||
{ |
|||
this._dbContext.Set<TEntity>().Where(predicate).ExecuteUpdate(setPropertyCalls); |
|||
} |
|||
|
|||
public void Delete(Expression<Func<TEntity, bool>> predicate) |
|||
{ |
|||
this._dbContext.Set<TEntity>().Where(predicate).ExecuteDelete(); |
|||
} |
|||
|
|||
public void Insert(TEntity entity) |
|||
{ |
|||
this._dbContext.Set<TEntity>().Add(entity); |
|||
} |
|||
|
|||
public void SaveChanges() |
|||
{ |
|||
this._dbContext.SaveChanges(); |
|||
} |
|||
|
|||
public void DisableSoftDeleteFilter() |
|||
{ |
|||
this._dbContext.GetType().GetProperty("DisableSoftDeleteFilter")?.SetValue(this._dbContext, true); |
|||
} |
|||
|
|||
public void DisableTenantFilter() |
|||
{ |
|||
this._dbContext.GetType().GetProperty("DisableTenantFilter")?.SetValue(this._dbContext, true); |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public interface IDbConfig<T> where T : DbContext |
|||
{ |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public interface IDbSeed<T> where T : DbContext |
|||
{ |
|||
void Seed(T context); |
|||
} |
@ -0,0 +1,26 @@ |
|||
using System.Linq.Expressions; |
|||
using Microsoft.EntityFrameworkCore.Query; |
|||
using WTA.Shared.Domain; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public interface IRepository<TEntity> where TEntity : BaseEntity |
|||
{ |
|||
IQueryable<TEntity> Queryable(); |
|||
|
|||
IQueryable<TEntity> AsNoTracking(); |
|||
|
|||
IQueryable<TEntity> AsNoTrackingWithIdentityResolution(); |
|||
|
|||
void Update(Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls, Expression<Func<TEntity, bool>> predicate); |
|||
|
|||
void Delete(Expression<Func<TEntity, bool>> predicate); |
|||
|
|||
void Insert(TEntity entity); |
|||
|
|||
void SaveChanges(); |
|||
|
|||
void DisableSoftDeleteFilter(); |
|||
|
|||
void DisableTenantFilter(); |
|||
} |
@ -0,0 +1,42 @@ |
|||
using WTA.Shared.Attributes; |
|||
|
|||
namespace WTA.Shared.Data; |
|||
|
|||
public enum OperatorType |
|||
{ |
|||
[Expression("{0} = @0")] |
|||
Equal, |
|||
|
|||
[Expression("{0} != @0")] |
|||
NotEqual, |
|||
|
|||
[Expression("{0} > @0")] |
|||
GreaterThan, |
|||
|
|||
[Expression("{0} >= @0")] |
|||
GreaterThanOrEqual, |
|||
|
|||
[Expression("{0} < @0")] |
|||
LessThan, |
|||
|
|||
[Expression("{0} <= @0")] |
|||
LessThanOrEqual, |
|||
|
|||
[Expression("{0}.Contains(@0)")] |
|||
Contains, |
|||
|
|||
[Expression("{0}.StartsWith(@0)")] |
|||
StartsWith, |
|||
|
|||
[Expression("{0}.EndsWith(@0)")] |
|||
EndsWith, |
|||
|
|||
//[Expression("{0}")]
|
|||
//OrderBy,
|
|||
|
|||
//[Expression("{0} desc")]
|
|||
//OrderByDesc
|
|||
|
|||
[Expression("{0}.EndsWith(@0)")] |
|||
Ignore, |
|||
} |
@ -0,0 +1,46 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; |
|||
|
|||
namespace WTA.Shared.DataAnnotations; |
|||
|
|||
public class CustomDisplayMetadataProvider : IDisplayMetadataProvider |
|||
{ |
|||
public void CreateDisplayMetadata(DisplayMetadataProviderContext context) |
|||
{ |
|||
var attributes = context.Attributes; |
|||
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault(); |
|||
if (displayAttribute != null && string.IsNullOrEmpty(displayAttribute.Name)) |
|||
{ |
|||
displayAttribute.Name = $"{context.Key.ContainerType?.Name}.{context.Key.Name}"; |
|||
} |
|||
//此处必须保留
|
|||
foreach (var item in attributes) |
|||
{ |
|||
if (item is ValidationAttribute attribute) |
|||
{ |
|||
if (attribute is DataTypeAttribute data && attribute.ErrorMessage != null) |
|||
{ |
|||
attribute.ErrorMessage = $"DataTypeAttribute_{data.GetDataTypeName()}"; |
|||
} |
|||
else if (item is RequiredAttribute required) |
|||
{ |
|||
required.ErrorMessage = nameof(RequiredAttribute); |
|||
} |
|||
else |
|||
{ |
|||
if (attribute.ErrorMessage == null) |
|||
{ |
|||
attribute.ErrorMessage = attribute.GetType().Name; |
|||
if (item is StringLengthAttribute stringLengthAttribute) |
|||
{ |
|||
if (stringLengthAttribute.MinimumLength != 0) |
|||
{ |
|||
attribute.ErrorMessage += "IncludingMinimum"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace WTA.Shared.DataAnnotations; |
|||
|
|||
public class CustomModelMetaDataProvider : DefaultModelMetadataProvider |
|||
{ |
|||
public CustomModelMetaDataProvider(ICompositeMetadataDetailsProvider detailsProvider, IOptions<MvcOptions> optionsAccessor) : base(detailsProvider, optionsAccessor) |
|||
{ |
|||
} |
|||
|
|||
protected override ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry) |
|||
{ |
|||
return new CustomModelMetadata(this, this.DetailsProvider, entry, this.ModelBindingMessageProvider); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; |
|||
using WTA.Shared.Extensions; |
|||
|
|||
namespace WTA.Shared.DataAnnotations; |
|||
|
|||
public class CustomModelMetadata : DefaultModelMetadata |
|||
{ |
|||
public CustomModelMetadata(IModelMetadataProvider provider, ICompositeMetadataDetailsProvider detailsProvider, DefaultMetadataDetails details, DefaultModelBindingMessageProvider modelBindingMessageProvider) : base(provider, detailsProvider, details, modelBindingMessageProvider) |
|||
{ |
|||
} |
|||
|
|||
public override string? DisplayName => this.ContainerType == null ? this.ModelType.GetDisplayName() : this.ContainerType?.GetProperty(this.PropertyName!)?.GetDisplayName() ?? this.GetDisplayName(); |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue