Browse Source

添加文档和UI原型菜单部分

pull/1/head
wanggang 1 year ago
commit
b1a69d50e7
  1. 536
      docs/demo/.editorconfig
  2. 3
      docs/demo/.gitattributes
  3. 16
      docs/demo/.gitignore
  4. 35
      docs/demo/.vscode/launch.json
  5. 41
      docs/demo/.vscode/tasks.json
  6. 48
      docs/demo/Demo.sln
  7. 39
      docs/demo/Directory.Packages.props
  8. 7
      docs/demo/NuGet.Config
  9. 37
      docs/demo/README.md
  10. 47
      docs/demo/src/WTA.Application/Captcha/CaptchaController.cs
  11. 14
      docs/demo/src/WTA.Application/Identity/Controllers/ConnectionStringController.cs
  12. 14
      docs/demo/src/WTA.Application/Identity/Controllers/PermissionController.cs
  13. 14
      docs/demo/src/WTA.Application/Identity/Controllers/TenantController.cs
  14. 208
      docs/demo/src/WTA.Application/Identity/Controllers/TokenController.cs
  15. 73
      docs/demo/src/WTA.Application/Identity/Controllers/UserController.cs
  16. 90
      docs/demo/src/WTA.Application/Identity/Data/IdentityConfiguration.cs
  17. 12
      docs/demo/src/WTA.Application/Identity/Data/IdentityDbContext.cs
  18. 257
      docs/demo/src/WTA.Application/Identity/Data/IdentityDbSeed.cs
  19. 642
      docs/demo/src/WTA.Application/Identity/Entities/BaseData/Class1.cs
  20. 12
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Department.cs
  21. 10
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/DictionaryItem.cs
  22. 32
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Permission.cs
  23. 18
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/PermissionType.cs
  24. 14
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Post.cs
  25. 14
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Role.cs
  26. 20
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/RolePermission.cs
  27. 9
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Token.cs
  28. 36
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/User.cs
  29. 16
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/UserRole.cs
  30. 8
      docs/demo/src/WTA.Application/Identity/Entities/SystemManagementAttribute.cs
  31. 17
      docs/demo/src/WTA.Application/Identity/Entities/Tenants/ConnectionString.cs
  32. 18
      docs/demo/src/WTA.Application/Identity/Entities/Tenants/Tenant.cs
  33. 8
      docs/demo/src/WTA.Application/Identity/Entities/TenantsAttribute.cs
  34. 16
      docs/demo/src/WTA.Application/Identity/IdentityModule.cs
  35. 40
      docs/demo/src/WTA.Application/Identity/Models/LoginRequestModel.cs
  36. 18
      docs/demo/src/WTA.Application/Identity/Models/LoginResponseModel.cs
  37. 9
      docs/demo/src/WTA.Application/Identity/Models/UserInfoModel.cs
  38. 35
      docs/demo/src/WTA.Application/Identity/TenantService.cs
  39. 46
      docs/demo/src/WTA.Application/Localization/LocalizationController.cs
  40. 12
      docs/demo/src/WTA.Application/Monitor/Controllers/Monitor.cs
  41. 30
      docs/demo/src/WTA.Application/Monitor/Controllers/MonitorController.cs
  42. 8
      docs/demo/src/WTA.Application/Monitor/Controllers/SystemMonitorAttribute.cs
  43. 15
      docs/demo/src/WTA.Application/Monitor/Entities/JobItem.cs
  44. 26
      docs/demo/src/WTA.Application/Monitor/Entities/UserLogin.cs
  45. 9
      docs/demo/src/WTA.Application/Monitor/MonitorModule.cs
  46. 60
      docs/demo/src/WTA.Application/Monitor/UserLoginSrevice.cs
  47. 7
      docs/demo/src/WTA.Application/WTA.Application.csproj
  48. 16
      docs/demo/src/WTA.Infrastructure/Resources/en.json
  49. 130
      docs/demo/src/WTA.Infrastructure/Resources/zh.json
  50. 19
      docs/demo/src/WTA.Infrastructure/WTA.Infrastructure.csproj
  51. 5
      docs/demo/src/WTA.Shared/Application/IExportModel.cs
  52. 5
      docs/demo/src/WTA.Shared/Application/IImportModel.cs
  53. 5
      docs/demo/src/WTA.Shared/Application/IResource.cs
  54. 5
      docs/demo/src/WTA.Shared/Application/IResourceService.cs
  55. 22
      docs/demo/src/WTA.Shared/Application/PaginationModel.cs
  56. 6
      docs/demo/src/WTA.Shared/Attributes/AddOnlyAttribute.cs
  57. 12
      docs/demo/src/WTA.Shared/Attributes/ComponentAttribute.cs
  58. 5
      docs/demo/src/WTA.Shared/Attributes/DbContextAttribute.cs
  59. 6
      docs/demo/src/WTA.Shared/Attributes/DisplayOnlyAttribute.cs
  60. 12
      docs/demo/src/WTA.Shared/Attributes/ExpressionAttribute.cs
  61. 12
      docs/demo/src/WTA.Shared/Attributes/GenericAttribute.cs
  62. 6
      docs/demo/src/WTA.Shared/Attributes/GroupAttribute.cs
  63. 6
      docs/demo/src/WTA.Shared/Attributes/HiddenAttribute.cs
  64. 13
      docs/demo/src/WTA.Shared/Attributes/HtmlClassAttribute.cs
  65. 14
      docs/demo/src/WTA.Shared/Attributes/IconAttribute.cs
  66. 6
      docs/demo/src/WTA.Shared/Attributes/IgnoreMultiTenancyAttribute.cs
  67. 6
      docs/demo/src/WTA.Shared/Attributes/IgnoreUpdateAttribute.cs
  68. 26
      docs/demo/src/WTA.Shared/Attributes/ImplementAttribute.cs
  69. 11
      docs/demo/src/WTA.Shared/Attributes/ImporterHeaderAttribute.cs
  70. 5
      docs/demo/src/WTA.Shared/Attributes/ModuleAttribute.cs
  71. 6
      docs/demo/src/WTA.Shared/Attributes/MultipleAttribute.cs
  72. 12
      docs/demo/src/WTA.Shared/Attributes/NavigationAttribute.cs
  73. 16
      docs/demo/src/WTA.Shared/Attributes/OperatorTypeAttribute.cs
  74. 12
      docs/demo/src/WTA.Shared/Attributes/OptionsAttribute.cs
  75. 13
      docs/demo/src/WTA.Shared/Attributes/OrderAttribute.cs
  76. 11
      docs/demo/src/WTA.Shared/Authentication/AuthenticateResult.cs
  77. 44
      docs/demo/src/WTA.Shared/Authentication/CustomClaimsPrincipal.cs
  78. 21
      docs/demo/src/WTA.Shared/Authentication/CustomJwtBearerPostConfigureOptions.cs
  79. 20
      docs/demo/src/WTA.Shared/Authentication/CustomJwtSecurityTokenHandler.cs
  80. 6
      docs/demo/src/WTA.Shared/Authentication/IAuthenticationService.cs
  81. 14
      docs/demo/src/WTA.Shared/Authentication/IdentityOptions.cs
  82. 20
      docs/demo/src/WTA.Shared/Captcha/CaptchaService.cs
  83. 6
      docs/demo/src/WTA.Shared/Captcha/ICaptchaService.cs
  84. 44
      docs/demo/src/WTA.Shared/Controllers/BaseController.cs
  85. 36
      docs/demo/src/WTA.Shared/Controllers/ControllerModelConvention.cs
  86. 193
      docs/demo/src/WTA.Shared/Controllers/GenericController.cs
  87. 32
      docs/demo/src/WTA.Shared/Controllers/GenericControllerFeatureProvider.cs
  88. 28
      docs/demo/src/WTA.Shared/Controllers/GenericControllerNameConventionAttribute.cs
  89. 54
      docs/demo/src/WTA.Shared/Controllers/GenericControllerRouteConvention.cs
  90. 10
      docs/demo/src/WTA.Shared/Controllers/HttpMethodDefaultAttribute.cs
  91. 12
      docs/demo/src/WTA.Shared/Controllers/SlugifyParameterTransformer.cs
  92. 237
      docs/demo/src/WTA.Shared/Data/BaseDbContext.cs
  93. 64
      docs/demo/src/WTA.Shared/Data/EfRepository.cs
  94. 7
      docs/demo/src/WTA.Shared/Data/IDbConfig.cs
  95. 8
      docs/demo/src/WTA.Shared/Data/IDbSeed.cs
  96. 26
      docs/demo/src/WTA.Shared/Data/IRepository.cs
  97. 42
      docs/demo/src/WTA.Shared/Data/OperatorType.cs
  98. 46
      docs/demo/src/WTA.Shared/DataAnnotations/CustomDisplayMetadataProvider.cs
  99. 18
      docs/demo/src/WTA.Shared/DataAnnotations/CustomModelMetaDataProvider.cs
  100. 14
      docs/demo/src/WTA.Shared/DataAnnotations/CustomModelMetadata.cs

536
docs/demo/.editorconfig

@ -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

3
docs/demo/.gitattributes

@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

16
docs/demo/.gitignore

@ -0,0 +1,16 @@
*.bak
#fe
node_modules/
dist/
#be
.vs/
bin/
obj/
*.suo
*.user
*.db
*.db-shm
*.db-wal

35
docs/demo/.vscode/launch.json

@ -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"
}
]
}

41
docs/demo/.vscode/tasks.json

@ -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"
}
]
}

48
docs/demo/Demo.sln

@ -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

39
docs/demo/Directory.Packages.props

@ -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>

7
docs/demo/NuGet.Config

@ -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>

37
docs/demo/README.md

@ -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 用于输入控件

47
docs/demo/src/WTA.Application/Captcha/CaptchaController.cs

@ -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
});
}
}

14
docs/demo/src/WTA.Application/Identity/Controllers/ConnectionStringController.cs

@ -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();
}
}

14
docs/demo/src/WTA.Application/Identity/Controllers/PermissionController.cs

@ -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();
}
}

14
docs/demo/src/WTA.Application/Identity/Controllers/TenantController.cs

@ -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();
}
}

208
docs/demo/src/WTA.Application/Identity/Controllers/TokenController.cs

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

73
docs/demo/src/WTA.Application/Identity/Controllers/UserController.cs

@ -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!;
}
}

90
docs/demo/src/WTA.Application/Identity/Data/IdentityConfiguration.cs

@ -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);
}
}

12
docs/demo/src/WTA.Application/Identity/Data/IdentityDbContext.cs

@ -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)
{
}
}

257
docs/demo/src/WTA.Application/Identity/Data/IdentityDbSeed.cs

@ -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();
});
}
}

642
docs/demo/src/WTA.Application/Identity/Entities/BaseData/Class1.cs

@ -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)
{ }
}

12
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Department.cs

@ -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>();
}

10
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/DictionaryItem.cs

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

32
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Permission.cs

@ -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>();
}

18
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/PermissionType.cs

@ -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
}

14
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Post.cs

@ -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>();
}

14
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Role.cs

@ -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>();
}

20
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/RolePermission.cs

@ -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>();
}

9
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/Token.cs

@ -0,0 +1,9 @@
using WTA.Shared.Application;
using WTA.Shared.Attributes;
namespace WTA.Application.Identity.Entities.SystemManagement;
[SystemManagement, Hidden]
public class Token : IResource
{
}

36
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/User.cs

@ -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>();
}

16
docs/demo/src/WTA.Application/Identity/Entities/SystemManagement/UserRole.cs

@ -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!;
}

8
docs/demo/src/WTA.Application/Identity/Entities/SystemManagementAttribute.cs

@ -0,0 +1,8 @@
using WTA.Shared.Attributes;
namespace WTA.Application.Identity.Entities;
[Order(1)]
public class SystemManagementAttribute : GroupAttribute
{
}

17
docs/demo/src/WTA.Application/Identity/Entities/Tenants/ConnectionString.cs

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

18
docs/demo/src/WTA.Application/Identity/Entities/Tenants/Tenant.cs

@ -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>();
}

8
docs/demo/src/WTA.Application/Identity/Entities/TenantsAttribute.cs

@ -0,0 +1,8 @@
using WTA.Shared.Attributes;
namespace WTA.Application.Identity.Entities;
//[Module<IdentityModule>]
public class TenantsAttribute : GroupAttribute
{
}

16
docs/demo/src/WTA.Application/Identity/IdentityModule.cs

@ -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);
}
}

40
docs/demo/src/WTA.Application/Identity/Models/LoginRequestModel.cs

@ -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" });
// }
//}
}

18
docs/demo/src/WTA.Application/Identity/Models/LoginResponseModel.cs

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

9
docs/demo/src/WTA.Application/Identity/Models/UserInfoModel.cs

@ -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>();
}

35
docs/demo/src/WTA.Application/Identity/TenantService.cs

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

46
docs/demo/src/WTA.Application/Localization/LocalizationController.cs

@ -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);
}
}

12
docs/demo/src/WTA.Application/Monitor/Controllers/Monitor.cs

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

30
docs/demo/src/WTA.Application/Monitor/Controllers/MonitorController.cs

@ -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());
}
}

8
docs/demo/src/WTA.Application/Monitor/Controllers/SystemMonitorAttribute.cs

@ -0,0 +1,8 @@
using WTA.Shared.Attributes;
namespace WTA.Application.Monitor.Controllers;
[Order(2)]
public class SystemMonitorAttribute : GroupAttribute
{
}

15
docs/demo/src/WTA.Application/Monitor/Entities/JobItem.cs

@ -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!;
}

26
docs/demo/src/WTA.Application/Monitor/Entities/UserLogin.cs

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

9
docs/demo/src/WTA.Application/Monitor/MonitorModule.cs

@ -0,0 +1,9 @@
using WTA.Shared.Attributes;
using WTA.Shared.Module;
namespace WTA.Application.Monitor;
[Order(100)]
public class MonitorModule : BaseModule
{
}

60
docs/demo/src/WTA.Application/Monitor/UserLoginSrevice.cs

@ -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);
}
}

7
docs/demo/src/WTA.Application/WTA.Application.csproj

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\WTA.Shared\WTA.Shared.csproj" />
</ItemGroup>
</Project>

16
docs/demo/src/WTA.Infrastructure/Resources/en.json

@ -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"
}

130
docs/demo/src/WTA.Infrastructure/Resources/zh.json

@ -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": "用户代理"
}

19
docs/demo/src/WTA.Infrastructure/WTA.Infrastructure.csproj

@ -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>

5
docs/demo/src/WTA.Shared/Application/IExportModel.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Application;
public interface IExportModel<TEntity> where TEntity : class
{
}

5
docs/demo/src/WTA.Shared/Application/IImportModel.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Application;
public interface IImportModel<TEntity> where TEntity : class
{
}

5
docs/demo/src/WTA.Shared/Application/IResource.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Application;
public interface IResource
{
}

5
docs/demo/src/WTA.Shared/Application/IResourceService.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Application;
public interface IResourceService<TResource> where TResource : IResource
{
}

22
docs/demo/src/WTA.Shared/Application/PaginationModel.cs

@ -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>();
}

6
docs/demo/src/WTA.Shared/Attributes/AddOnlyAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public class AddOnlyAttribute : Attribute
{
}

12
docs/demo/src/WTA.Shared/Attributes/ComponentAttribute.cs

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

5
docs/demo/src/WTA.Shared/Attributes/DbContextAttribute.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Attributes;
public class DbContextAttribute<T> : GenericAttribute<T>
{
}

6
docs/demo/src/WTA.Shared/Attributes/DisplayOnlyAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public class DisplayOnlyAttribute : Attribute
{
}

12
docs/demo/src/WTA.Shared/Attributes/ExpressionAttribute.cs

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

12
docs/demo/src/WTA.Shared/Attributes/GenericAttribute.cs

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

6
docs/demo/src/WTA.Shared/Attributes/GroupAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class GroupAttribute : Attribute
{
}

6
docs/demo/src/WTA.Shared/Attributes/HiddenAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class HiddenAttribute : Attribute
{
}

13
docs/demo/src/WTA.Shared/Attributes/HtmlClassAttribute.cs

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

14
docs/demo/src/WTA.Shared/Attributes/IconAttribute.cs

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

6
docs/demo/src/WTA.Shared/Attributes/IgnoreMultiTenancyAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreMultiTenancyAttribute : Attribute
{
}

6
docs/demo/src/WTA.Shared/Attributes/IgnoreUpdateAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreUpdateAttribute : Attribute
{
}

26
docs/demo/src/WTA.Shared/Attributes/ImplementAttribute.cs

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

11
docs/demo/src/WTA.Shared/Attributes/ImporterHeaderAttribute.cs

@ -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!;
}

5
docs/demo/src/WTA.Shared/Attributes/ModuleAttribute.cs

@ -0,0 +1,5 @@
namespace WTA.Shared.Attributes;
public class ModuleAttribute<T> : GenericAttribute<T>
{
}

6
docs/demo/src/WTA.Shared/Attributes/MultipleAttribute.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Attributes;
[AttributeUsage(AttributeTargets.Method)]
public class MultipleAttribute : Attribute
{
}

12
docs/demo/src/WTA.Shared/Attributes/NavigationAttribute.cs

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

16
docs/demo/src/WTA.Shared/Attributes/OperatorTypeAttribute.cs

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

12
docs/demo/src/WTA.Shared/Attributes/OptionsAttribute.cs

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

13
docs/demo/src/WTA.Shared/Attributes/OrderAttribute.cs

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

11
docs/demo/src/WTA.Shared/Authentication/AuthenticateResult.cs

@ -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>();
}

44
docs/demo/src/WTA.Shared/Authentication/CustomClaimsPrincipal.cs

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

21
docs/demo/src/WTA.Shared/Authentication/CustomJwtBearerPostConfigureOptions.cs

@ -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);
}
}

20
docs/demo/src/WTA.Shared/Authentication/CustomJwtSecurityTokenHandler.cs

@ -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));
}
}

6
docs/demo/src/WTA.Shared/Authentication/IAuthenticationService.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Authentication;
public interface IAuthenticationService
{
AuthenticateResult Authenticate(string name, string operation);
}

14
docs/demo/src/WTA.Shared/Authentication/IdentityOptions.cs

@ -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);
}

20
docs/demo/src/WTA.Shared/Captcha/CaptchaService.cs

@ -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())}";
}
}

6
docs/demo/src/WTA.Shared/Captcha/ICaptchaService.cs

@ -0,0 +1,6 @@
namespace WTA.Shared.Captcha;
public interface ICaptchaService
{
string Create(string code);
}

44
docs/demo/src/WTA.Shared/Controllers/BaseController.cs

@ -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);
}
}

36
docs/demo/src/WTA.Shared/Controllers/ControllerModelConvention.cs

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

193
docs/demo/src/WTA.Shared/Controllers/GenericController.cs

@ -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);
}
}
}

32
docs/demo/src/WTA.Shared/Controllers/GenericControllerFeatureProvider.cs

@ -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());
}
}
}
}

28
docs/demo/src/WTA.Shared/Controllers/GenericControllerNameConventionAttribute.cs

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

54
docs/demo/src/WTA.Shared/Controllers/GenericControllerRouteConvention.cs

@ -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 }));
}
}
});
}
}
}

10
docs/demo/src/WTA.Shared/Controllers/HttpMethodDefaultAttribute.cs

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc.Routing;
namespace WTA.Shared.Controllers;
public class HttpMethodDefaultAttribute : HttpMethodAttribute
{
public HttpMethodDefaultAttribute(IEnumerable<string> httpMethods) : base(httpMethods)
{
}
}

12
docs/demo/src/WTA.Shared/Controllers/SlugifyParameterTransformer.cs

@ -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();
}
}

237
docs/demo/src/WTA.Shared/Data/BaseDbContext.cs

@ -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 ?? "";
}
}

64
docs/demo/src/WTA.Shared/Data/EfRepository.cs

@ -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);
}
}

7
docs/demo/src/WTA.Shared/Data/IDbConfig.cs

@ -0,0 +1,7 @@
using Microsoft.EntityFrameworkCore;
namespace WTA.Shared.Data;
public interface IDbConfig<T> where T : DbContext
{
}

8
docs/demo/src/WTA.Shared/Data/IDbSeed.cs

@ -0,0 +1,8 @@
using Microsoft.EntityFrameworkCore;
namespace WTA.Shared.Data;
public interface IDbSeed<T> where T : DbContext
{
void Seed(T context);
}

26
docs/demo/src/WTA.Shared/Data/IRepository.cs

@ -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();
}

42
docs/demo/src/WTA.Shared/Data/OperatorType.cs

@ -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,
}

46
docs/demo/src/WTA.Shared/DataAnnotations/CustomDisplayMetadataProvider.cs

@ -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";
}
}
}
}
}
}
}
}

18
docs/demo/src/WTA.Shared/DataAnnotations/CustomModelMetaDataProvider.cs

@ -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);
}
}

14
docs/demo/src/WTA.Shared/DataAnnotations/CustomModelMetadata.cs

@ -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…
Cancel
Save