wanggang
2 years 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