diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..89174ffc --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Build and Release Folders +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ + +# Other files and folders +.settings/ + +# Executables +*.swf +*.air +*.ipa +*.apk + +# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` +# should NOT be excluded as they contain compiler settings and other important +# information for Eclipse / Flash Builder. +/Admin.NET/.vs +/Admin.NET/packages +/Admin.NET/Admin.NET.Web.Entry/wwwroot/Upload +/Admin.NET/Admin.NET.Web.Entry/wwwroot/Avatar +/Admin.NET/Admin.NET.Web.Entry/wwwroot/Signature +/Admin.NET/Admin.NET.Web.Entry/wwwroot/CodeGen +/Admin.NET/Admin.NET.Web.Entry/wwwroot/is-cache +/Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj.user +/Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj.user +/Admin.NET/Admin.NET.Web.Entry/Properties/PublishProfiles +/Admin.NET/Admin.NET.Web.Entry/publish +/Admin.NET/Admin.NET.Web.Entry/logs +/Admin.NET/Admin.NET.Web.Entry/Admin.NET.db + +# folders +dist/ +node_modules/ +/Web/package-lock.json +/Web/public/config.js +/Web/stats.html +/Web/npminstall-debug.log +.vs +.idea +.DS_Store +/Admin.NET/.vs +/Admin.NET.App/unpackage +/App/unpackage diff --git a/Admin.NET/.dockerignore b/Admin.NET/.dockerignore new file mode 100644 index 00000000..3729ff0c --- /dev/null +++ b/Admin.NET/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Admin.NET/.editorconfig b/Admin.NET/.editorconfig new file mode 100644 index 00000000..aab71a9f --- /dev/null +++ b/Admin.NET/.editorconfig @@ -0,0 +1,178 @@ + +[*.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_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_elsewhere = false:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_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 +csharp_space_around_binary_operators = before_and_after +csharp_indent_labels = one_less_than_current + +[*.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 + +[*.{cs,vb}] +end_of_line = crlf +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +indent_size = 4 +tab_width = 4 +dotnet_style_operator_placement_when_wrapping = beginning_of_line + +# Add copyright file header +file_header_template = Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n\n本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n\n不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj b/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj new file mode 100644 index 00000000..2ccdc952 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj @@ -0,0 +1,43 @@ + + + + net6.0;net8.0 + 1701;1702;1591;8632 + + enable + True + disable + Admin.NET + Admin.NET 通用权限开发平台 + + + + + true + PreserveNewest + PreserveNewest + + + true + PreserveNewest + PreserveNewest + + + + + + + + + + + + + + + + + + + + diff --git a/Admin.NET/Admin.NET.Application/Configuration/APIJSON.json b/Admin.NET/Admin.NET.Application/Configuration/APIJSON.json new file mode 100644 index 00000000..5baf2316 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/APIJSON.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "APIJSON": { + "Roles": [ + { + "RoleName": "Role1", // 权限名称 唯一 + "Select": { // 查询 + "Table": [ "*" ], // 可操作的表 + "Column": [ "*" ], // 可操作的字段 + "Filter": [] + }, + "Insert": { // 添加 + "Table": [ "table1", "table2", "table3" ], + "Column": [ "*", "*", "tb.*" ] + }, + "Update": { // 修改 + "Table": [ "table1", "table2", "table3" ], + "Column": [ "*", "tb.*", "tb.*" ] + }, + "Delete": { // 删除 + "Table": [ "table1", "table2", "table3" ] + } + }, + { + "RoleName": "Role2", + "Select": { + "Table": [ "table1" ], + "Column": [ "tb.*" ] + } + } + ] + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/App.json b/Admin.NET/Admin.NET.Application/Configuration/App.json new file mode 100644 index 00000000..d893a54b --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/App.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Urls": "http://*:5005", // 配置默认端口 + // "https_port": 44325, + + "AllowedHosts": "*", + + "AppSettings": { + "InjectSpecificationDocument": true, // 生产环境是否开启Swagger + "ExternalAssemblies": [ "plugins" ] // 插件目录 + }, + "DynamicApiControllerSettings": { + //"DefaultRoutePrefix": "api", // 默认路由前缀 + "CamelCaseSeparator": "", // 驼峰命名分隔符 + "SplitCamelCase": false, // 切割骆驼(驼峰)/帕斯卡命名 + "LowercaseRoute": false, // 小写路由格式 + "AsLowerCamelCase": true, // 小驼峰命名(首字母小写) + "KeepVerb": false, // 保留动作方法请求谓词 + "KeepName": false // 保持原有名称不处理 + }, + "FriendlyExceptionSettings": { + "DefaultErrorMessage": "系统异常,请联系管理员", + "ThrowBah": true, // 是否将 Oops.Oh 默认抛出为业务异常 + "LogError": false // 是否输出异常日志 + }, + "LocalizationSettings": { + "SupportedCultures": [ "zh-CN", "en" ], // 语言列表 + "DefaultCulture": "zh-CN", // 默认语言 + "DateTimeFormatCulture": "zh-CN" // 固定时间区域为特定时区(多语言) + }, + "CorsAccessorSettings": { + //"PolicyName": "App.Cors.Policy", + //"WithOrigins": [ "http://localhost:5005", "https://gitee.com" ], + "WithExposedHeaders": [ "Content-Disposition", "X-Pagination", "access-token", "x-access-token" ], // 如果前端不代理且是axios请求 + "SignalRSupport": true // 启用 SignalR 跨域支持 + }, + "SnowId": { + "WorkerId": 1, // 机器码 全局唯一 + "WorkerIdBitLength": 6, // 机器码位长 默认值6,取值范围 [1, 19] + "SeqBitLength": 6, // 序列数位长 默认值6,取值范围 [3, 21](建议不小于4,值越大性能越高、Id位数也更长) + "WorkerPrefix": "adminnet_" // 缓存前缀 + }, + "Cryptogram": { + "StrongPassword": false, // 是否开启密码强度验证 + "PasswordStrengthValidation": "(?=^.{6,16}$)(?=.*\\d)(?=.*\\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\\n).*$", // 密码强度验证正则表达式,必须须包含大小写字母、数字和特殊字符的组合,长度在6-16之间 + "PasswordStrengthValidationMsg": "密码必须包含大小写字母、数字和特殊字符的组合,长度在6-16之间", // 密码强度验证消息提示 + "CryptoType": "SM2", // 密码加密算法:MD5、SM2、SM4 + "PublicKey": "04851D329AA3E38C2E7670AFE70E6E70E92F8769CA27C8766B12209A0FFBA4493B603EF7A0B9B1E16F0E8930C0406EA0B179B68DF28E25334BDEC4AE76D907E9E9", // 公钥 + "PrivateKey": "3A61D1D30C6302DABFF36201D936D0143EEF0C850AF28C5CA6D5C045AF8C5C8A" // 私钥 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Cache.json b/Admin.NET/Admin.NET.Application/Configuration/Cache.json new file mode 100644 index 00000000..de9969bd --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Cache.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Cache": { + "Prefix": "adminnet_", // 全局缓存前缀 + "CacheType": "Memory", // Memory、Redis + "Redis": { + "Configuration": "server=127.0.0.1:6379;password=;db=5;", // Redis连接字符串 + "Prefix": "adminnet_", // Redis前缀(目前没用) + "MaxMessageSize": "1048576" // 最大消息大小 默认1024 * 1024 + } + }, + "Cluster": { // 集群配置 + "Enabled": false, // 启用集群:前提开启Redis缓存模式 + "ServerId": "adminnet", // 服务器标识 + "ServerIp": "", // 服务器IP + "SignalR": { + "RedisConfiguration": "127.0.0.1:6379,ssl=false,password=,defaultDatabase=5", + "ChannelPrefix": "signalrPrefix_" + }, + "DataProtecteKey": "AdminNet:DataProtection-Keys", + "IsSentinel": false, // 是否哨兵模式 + "SentinelConfig": { + "DefaultDb": "4", + "EndPoints": [ // 哨兵端口 + // "10.10.0.124:26380" + ], + "MainPrefix": "adminNet:", + "Password": "123456", + "SentinelPassword": "adminNet", + "ServiceName": "adminNet", + "SignalRChannelPrefix": "signalR:" + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Captcha.json b/Admin.NET/Admin.NET.Application/Configuration/Captcha.json new file mode 100644 index 00000000..0d8646fd --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Captcha.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "CaptchaOptions": { + "CaptchaType": 10, // 验证码类型0、1、2、3、4、5、6、7、8、9、10、11 + "CodeLength": 1, // 验证码长度, 要放在CaptchaType设置后 当类型为算术表达式时,长度代表操作的个数, 例如2 + "ExpirySeconds": 60, // 验证码过期秒数 + "IgnoreCase": true, // 比较时是否忽略大小写 + "StoreageKeyPrefix": "", // 存储键前缀 + "ImageOption": { + "Animation": true, // 是否启用动画 + "FontSize": 36, // 字体大小 + "Width": 150, // 验证码宽度 + "Height": 50, // 验证码高度 + "BubbleMinRadius": 5, // 气泡最小半径 + "BubbleMaxRadius": 10, // 气泡最大半径 + "BubbleCount": 3, // 气泡数量 + "BubbleThickness": 1.0, // 气泡边沿厚度 + "InterferenceLineCount": 3, // 干扰线数量 + "FontFamily": "kaiti", // 包含actionj,epilog,fresnel,headache,lexo,prefix,progbot,ransom,robot,scandal,kaiti + "FrameDelay": 300, // 每帧延迟,Animation=true时有效, 默认300 + "BackgroundColor": "#ffffff", // 格式: rgb, rgba, rrggbb, or rrggbbaa format to match web syntax, 默认#fff + "ForegroundColors": "", // 颜色格式同BackgroundColor,多个颜色逗号分割,随机选取。不填,空值,则使用默认颜色集 + "Quality": 100, // 图片质量(质量越高图片越大,gif调整无效可能会更大) + "TextBold": true // 粗体 + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/CodeGen.json b/Admin.NET/Admin.NET.Application/Configuration/CodeGen.json new file mode 100644 index 00000000..275087a8 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/CodeGen.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + // 代码生成配置项-程序集名称集合 + "CodeGen": { + "EntityAssemblyNames": [ "Admin.NET.Core", "Admin.NET.Application" ], + "BaseEntityNames": [ "EntityTenantId", "EntityTenant", "EntityTenantBaseData", "EntityBaseData", "EntityBase", "EntityBaseId" ], + "EntityBaseColumn": { + "EntityTenantId": [ "Id", "TenantId" ], + "EntityTenant": [ "Id", "CreateTime", "UpdateTime", "CreateUserId", "UpdateUserId", "CreateUserName", "UpdateUserName", "IsDelete", "TenantId" ], + "EntityTenantBaseData": [ "Id", "CreateTime", "UpdateTime", "CreateUserId", "UpdateUserId", "CreateUserName", "UpdateUserName", "IsDelete", "CreateOrgId", "CreateOrgName", "TenantId" ], + "EntityBaseData": [ "Id", "CreateTime", "UpdateTime", "CreateUserId", "UpdateUserId", "CreateUserName", "UpdateUserName", "IsDelete", "CreateOrgId", "CreateOrgName" ], + "EntityBase": [ "Id", "CreateTime", "UpdateTime", "CreateUserId", "UpdateUserId", "CreateUserName", "UpdateUserName", "IsDelete" ], + "EntityBaseId": [ "Id" ] + //"BaseId": [ "Id" ] + }, + "FrontRootPath": "Web", // 前端项目根目录 + "BackendApplicationNamespaces": [ "Admin.NET.Application", "Admin.NET.Application2" ] // 后端生成到的项目 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Database.json b/Admin.NET/Admin.NET.Application/Configuration/Database.json new file mode 100644 index 00000000..5e5401a5 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Database.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + // 详细数据库配置见SqlSugar官网(第一个为默认库) + "DbConnection": { + "EnableConsoleSql": true, // 启用控制台打印SQL + "ConnectionConfigs": [ + { + //"ConfigId": "1300000000001", // 默认库标识-禁止修改 + "DbType": "Sqlite", // MySql、SqlServer、Sqlite、Oracle、PostgreSQL、Dm、Kdbndp、Oscar、MySqlConnector、Access、OpenGauss、QuestDB、HG、ClickHouse、GBase、Odbc、Custom + "ConnectionString": "DataSource=./Admin.NET.db", // 库连接字符串 + //"SlaveConnectionConfigs": [ // 读写分离/主从 + // { + // "HitRate": 10, + // "ConnectionString": "DataSource=./Admin.NET1.db" + // }, + // { + // "HitRate": 10, + // "ConnectionString": "DataSource=./Admin.NET2.db" + // } + //], + "DbSettings": { + "EnableInitDb": true, // 启用库初始化 + "EnableDiffLog": false, // 启用库表差异日志 + "EnableUnderLine": false // 启用驼峰转下划线 + }, + "TableSettings": { + "EnableInitTable": true, // 启用表初始化 + "EnableIncreTable": false // 启用表增量更新-特性[IncreTable] + }, + "SeedSettings": { + "EnableInitSeed": true, // 启用种子初始化 + "EnableIncreSeed": false // 启用种子增量更新-特性[IncreSeed] + } + } + //// 日志独立数据库配置 + //{ + // "ConfigId": "1300000000002", // 日志库标识-禁止修改 + // "DbType": "Sqlite", + // "ConnectionString": "DataSource=./Admin.NET.Log.db", // 库连接字符串 + // "DbSettings": { + // "EnableInitDb": true, // 启用库初始化 + // "EnableDiffLog": false, // 启用库表差异日志 + // "EnableUnderLine": false // 启用驼峰转下划线 + // }, + // "TableSettings": { + // "EnableInitTable": true, // 启用表初始化 + // "EnableIncreTable": false // 启用表增量更新-特性[IncreTable] + // }, + // "SeedSettings": { + // "EnableInitSeed": false, // 启用种子初始化 + // "EnableIncreSeed": false // 启用种子增量更新-特性[IncreSeed] + // } + //}, + //// 其他数据库配置(可以配置多个) + //{ + // "ConfigId": "test", // 库标识 + // "DbType": "Sqlite", // 库类型 + // "ConnectionString": "DataSource=./Admin.NET.Test.db", // 库连接字符串 + // "DbSettings": { + // "EnableInitDb": true, // 启用库初始化 + // "EnableDiffLog": false, // 启用库表差异日志 + // "EnableUnderLine": false // 启用驼峰转下划线 + // }, + // "TableSettings": { + // "EnableInitTable": true, // 启用表初始化 + // "EnableIncreTable": false // 启用表增量更新-特性[IncreTable] + // }, + // "SeedSettings": { + // "EnableInitSeed": true, // 启用种子初始化 + // "EnableIncreSeed": false // 启用种子增量更新-特性[IncreSeed] + // } + //} + ] + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Email.json b/Admin.NET/Admin.NET.Application/Configuration/Email.json new file mode 100644 index 00000000..bc53ba93 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Email.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Email": { + "Host": "smtp.163.com", // 主机 + "Port": 465, // 端口 465、994、25 + "EnableSsl": true, // 启用SSL + "DefaultFromEmail": "xxx@163.com", // 默认发件者邮箱 + "DefaultToEmail": "xxx@qq.com", // 默认接收人邮箱 + "UserName": "xxx@163.com", // 邮箱账号 + "Password": "", // 邮箱授权码 + "DefaultFromName": "Admin.NET 通用权限开发平台" // 默认邮件标题 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Enum.json b/Admin.NET/Admin.NET.Application/Configuration/Enum.json new file mode 100644 index 00000000..dbcbbb11 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Enum.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + // 枚举实体所在程序集名称集合 + "Enum": { + "EntityAssemblyNames": [ "Admin.NET.Core", "Admin.NET.Application", "Admin.NET.AppCMS" ] + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/EventBus.json b/Admin.NET/Admin.NET.Application/Configuration/EventBus.json new file mode 100644 index 00000000..520123e8 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/EventBus.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "EventBus": { + "RabbitMQ": { + "UserName": "adminnet", + "Password": "adminnet++123456", + "HostName": "127.0.0.1", + "Port": 5672 + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/JWT.json b/Admin.NET/Admin.NET.Application/Configuration/JWT.json new file mode 100644 index 00000000..1864eaa4 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/JWT.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "JWTSettings": { + "ValidateIssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true + "IssuerSigningKey": "3F025D682370B0126BBAE7A93D9B66CE3F025D682370B0126BBAE7A93D9B66CE", // 密钥,string 类型,必须是复杂密钥,长度大于16,.NET8+ 长度需大于 32,推荐MD5直接生成 + "ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true + "ValidIssuer": "Admin.NET", // 签发方,string 类型 + "ValidateAudience": true, // 是否验证签收方,bool 类型,默认true + "ValidAudience": "Admin.NET", // 签收方,string 类型 + "ValidateLifetime": true, // 是否验证过期时间,bool 类型,默认true,建议true + //"ExpiredTime": 20, // 过期时间,long 类型,单位分钟,默认20分钟,最大支持 13 年 + "ClockSkew": 5, // 过期时间容错值,long 类型,单位秒,默认5秒 + "Algorithm": "HS256", // 加密算法,string 类型,默认 HS256 + "RequireExpirationTime": true // 验证过期时间,设置 false 将永不过期 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Limit.json b/Admin.NET/Admin.NET.Application/Configuration/Limit.json new file mode 100644 index 00000000..9bce62cb --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Limit.json @@ -0,0 +1,121 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + // IP限流配置 + "IpRateLimiting": { + // 例如:设置每分钟5次访问限流 + // 当False时:每个接口都加入计数,不管你访问哪个接口,只要在一分钟内累计够5次,将禁止访问。 + // 当True 时:当一分钟请求了5次GetData接口,则该接口将在时间段内禁止访问,但是还可以访问PostData()5次,总得来说是每个接口都有5次在这一分钟,互不干扰。 + "EnableEndpointRateLimiting": true, + // 如果StackBlockedRequests设置为false,拒绝的API调用不会添加到调用次数计数器上。比如:如果客户端每秒发出3个请求并且您设置了每秒一个调用的限制, + // 则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等),则必须设置 + "StackBlockedRequests": false, + // 在RealIpHeader使用时,你的Kestrel服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置它。 + "RealIpHeader": "X-Real-IP", + // 将ClientIdHeader被用于提取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。 + "ClientIdHeader": "X-ClientId", + // IP白名单:支持Ipv4和Ipv6 + "IpWhitelist": [], + // 端点白名单 + "EndpointWhitelist": [], + // 客户端白名单 + "ClientWhitelist": [], + "QuotaExceededResponse": { + "Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问过于频繁,请稍后重试!\",\"result\":null,\"extras\":null}}", + "ContentType": "application/json", + "StatusCode": 429 + }, + // 返回状态码 + "HttpStatusCode": 429, + // API规则,结尾一定要带* + "GeneralRules": [ + // 1秒钟只能调用10次 + { + "Endpoint": "*", + "Period": "1s", + "Limit": 10 + }, + // 1分钟只能调用600次 + { + "Endpoint": "*", + "Period": "1m", + "Limit": 600 + }, + // 1小时只能调用3600 + { + "Endpoint": "*", + "Period": "1h", + "Limit": 3600 + }, + // 1天只能调用86400次 + { + "Endpoint": "*", + "Period": "1d", + "Limit": 86400 + } + ] + }, + "IpRateLimitPolicies": { + "IpRules": [ + { + "Ip": "XXX.XXX.XXX.XXX", + "Rules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": "*", + "Period": "1m", + "Limit": 600 + } + ] + } + ] + }, + // 客户端限流配置 + "ClientRateLimiting": { + "EnableEndpointRateLimiting": true, + "ClientIdHeader": "X-ClientId", + "EndpointWhitelist": [], + "ClientWhitelist": [], + "QuotaExceededResponse": { + "Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问人数过多,请稍后重试!\",\"result\":null,\"extras\":null}}", + "ContentType": "application/json", + "StatusCode": 429 + }, + "HttpStatusCode": 429, + "GeneralRules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": "*", + "Period": "1m", + "Limit": 600 + } + ] + }, + "ClientRateLimitPolicies": { + "ClientRules": [ + { + "ClientId": "xxx-xxx", + "Rules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": "*", + "Period": "1m", + "Limit": 600 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Logging.json b/Admin.NET/Admin.NET.Application/Configuration/Logging.json new file mode 100644 index 00000000..5dee96c3 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Logging.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Information" + }, + "File": { + "Enabled": false, // 启用文件日志 + "FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件 + "Append": true, // 追加覆盖 + // "MinimumLevel": "Information", // 日志级别 + "FileSizeLimitBytes": 10485760, // 10M=10*1024*1024 + "MaxRollingFiles": 30 // 只保留30个文件 + }, + "Database": { + "Enabled": true, // 启用数据库日志 + "MinimumLevel": "Information" + }, + "ElasticSearch": { + "Enabled": false, // 启用ES日志 + "AuthType": "Basic", // ES认证类型,可选 Basic、ApiKey、Base64ApiKey + "User": "admin", // Basic认证的用户名,使用Basic认证类型时必填 + "Password": "123456", // Basic认证的密码,使用Basic认证类型时必填 + "ApiId": "", // 使用ApiKey认证类型时必填 + "ApiKey": "", // 使用ApiKey认证类型时必填 + "Base64ApiKey": "TmtrOEszNEJuQ0NyaWlydGtROFk6SG1RZ0w3YzBTc2lCanJTYlV3aXNzZw==", // 使用Base64ApiKey认证类型时必填 + "Fingerprint": "37:08:6A:C6:06:CC:9A:43:CF:ED:25:A2:1C:A4:69:57:90:31:2C:06:CA:61:56:39:6A:9C:46:11:BD:22:51:DA", // ES使用Https时的证书指纹 + "ServerUris": [ "http://192.168.1.100:9200" ], // 地址 + "DefaultIndex": "adminnet" // 索引 + }, + "Monitor": { + "GlobalEnabled": true, // 启用全局拦截日志 + "IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效 + "ExcludeOfMethods": [], // 排除特定方法,当GlobalEnabled=true有效 + "BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别 + "WithReturnValue": true, // 是否包含返回值,默认true + "ReturnValueThreshold": 500, // 返回值字符串阈值,默认0全量输出 + "JsonBehavior": "None", // 是否输出Json,默认None(OnlyJson、All) + "JsonIndented": false, // 是否格式化Json + "UseUtcTimestamp": false, // 时间格式UTC、LOCAL + "ConsoleLog": true // 是否显示控制台日志 + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/OAuth.json b/Admin.NET/Admin.NET.Application/Configuration/OAuth.json new file mode 100644 index 00000000..f2f28aa1 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/OAuth.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "OAuth": { + "Weixin": { + "ClientId": "xxx", + "ClientSecret": "xxx" + }, + "Gitee": { + "ClientId": "xxx", + "ClientSecret": "xxx" + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/SMS.json b/Admin.NET/Admin.NET.Application/Configuration/SMS.json new file mode 100644 index 00000000..f126bff1 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/SMS.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "SMS": { + "Aliyun": { + "AccessKeyId": "", + "AccessKeySecret": "", + "SignName": "AdminNET 平台", // 短信签名 + "TemplateCode": "" // 短信模板 + }, + "Tencentyun": { + "SdkAppId": "", + "AccessKeyId": "", + "AccessKeySecret": "", + "SignName": "AdminNET 平台", // 短信签名 + "TemplateCode": "" // 短信模板 + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Swagger.json b/Admin.NET/Admin.NET.Application/Configuration/Swagger.json new file mode 100644 index 00000000..b8179caa --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Swagger.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "SpecificationDocumentSettings": { + "DocumentTitle": "Admin.NET 通用权限开发平台", + "GroupOpenApiInfos": [ + { + "Group": "Default", + "Title": "Admin.NET 通用权限开发平台", + "Description": "让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。", + "Version": "1.0.0", + "Order": 1000 + }, + { + "Group": "All Groups", + "Title": "所有接口", + "Description": "让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。", + "Version": "1.0.0", + "Order": 0 + } + ], + "DefaultGroupName": "Default", // 默认分组名 + "DocExpansionState": "List", // List、Full、None + "EnableAllGroups": true, + "LoginInfo": { + "Enabled": true, // 是否开启Swagger登录 + "CheckUrl": "/api/swagger/checkUrl", + "SubmitUrl": "/api/swagger/submitUrl" + }, + "EnumToNumber": true // 枚举类型生成值类型 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Upload.json b/Admin.NET/Admin.NET.Application/Configuration/Upload.json new file mode 100644 index 00000000..85936520 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Upload.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Upload": { + "Path": "Upload/{yyyy}/{MM}/{dd}", // 文件上传目录 + "MaxSize": 20480, // 文件最大限制KB:1024*20 + "ContentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif", "image/bmp", "text/plain", "application/pdf", "application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "video/mp4", "application/wps-office.docx", "application/wps-office.xlsx" ], + "EnableMd5": false // 启用文件MDF5验证-防止重复上传 + }, + "OSSProvider": { + "IsEnable": false, + "Provider": "Minio", // OSS提供者 Invalid/Minio/Aliyun/QCloud/Qiniu/HuaweiCloud + "Endpoint": "xxx.xxx.xxx.xxx:8090", // 节点/API地址(在腾讯云OSS中表示AppId) + "Region": "xxx.xxx.xxx.xxx", // 地域 + "AccessKey": "", + "SecretKey": "", + "IsEnableHttps": false, // 是否启用HTTPS + "IsEnableCache": true, // 是否启用缓存 + "Bucket": "admin.net" + }, + "SSHProvider": { + "IsEnable": false, + "Host": "127.0.0.1", + "Port": 8222, + "Username": "sshuser", + "Password": "Password.1" + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Configuration/Wechat.json b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json new file mode 100644 index 00000000..b642ffd1 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "Wechat": { + // 公众号 + "WechatAppId": "", + "WechatAppSecret": "", + "WechatToken": "", // 微信公众号服务器配置中的令牌(Token) + "WechatEncodingAESKey": "", // 微信公众号服务器配置中的消息加解密密钥(EncodingAESKey) + // 小程序 + "WxOpenAppId": "", + "WxOpenAppSecret": "", + "WxToken": "", // 小程序消息推送中的令牌(Token) + "WxEncodingAESKey": "" // 小程序消息推送中的消息加解密密钥(EncodingAESKey) + }, + // 微信支付 + "WechatPay": { + "AppId": "", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId + "MerchantId": "", // 商户平台的商户号 + "MerchantV3Secret": "", // 商户平台的APIv3密钥 + "MerchantCertificateSerialNumber": "", // 商户平台的证书序列号 + "MerchantCertificatePrivateKey": "\\WxPayCert\\apiclient_key.pem" // 商户平台的API证书私钥(apiclient_key.pem文件内容) + }, + // 支付回调 + "PayCallBack": { + "WechatPayUrl": "https://xxx/api/sysWechatPay/payCallBack", // 微信支付回调 + "WechatRefundUrl": "", // 微信退款回调 + "AlipayUrl": "", // 支付宝支付回调 + "AlipayRefundUrl": "" // 支付宝退款回调 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Const/AppClaimConst.cs b/Admin.NET/Admin.NET.Application/Const/AppClaimConst.cs new file mode 100644 index 00000000..939eb975 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Const/AppClaimConst.cs @@ -0,0 +1,15 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Application; + +/// +/// 移动端Claim相关常量 +/// +public class AppClaimConst : ClaimConst +{ + +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Const/ApplicationConst.cs b/Admin.NET/Admin.NET.Application/Const/ApplicationConst.cs new file mode 100644 index 00000000..70ee2b6b --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Const/ApplicationConst.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Application; + +/// +/// 业务应用相关常量 +/// +public class ApplicationConst +{ + /// + /// API分组名称 + /// + public const string GroupName = "xxx业务应用"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/GlobalUsings.cs b/Admin.NET/Admin.NET.Application/GlobalUsings.cs new file mode 100644 index 00000000..7dd67391 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/GlobalUsings.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +global using Admin.NET.Core; +global using Furion; +global using Furion.DependencyInjection; +global using Furion.DynamicApiController; +global using Furion.FriendlyException; +global using Mapster; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.Extensions.DependencyInjection; +global using SqlSugar; +global using System; +global using System.Collections.Generic; +global using System.ComponentModel; +global using System.ComponentModel.DataAnnotations; +global using System.Threading.Tasks; +global using System.Linq; \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs b/Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs new file mode 100644 index 00000000..22b91b3b --- /dev/null +++ b/Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs @@ -0,0 +1,28 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Application; + +///// +///// 示例开放接口 +///// +//[ApiDescriptionSettings("开放接口", Name = "Demo", Order = 100)] +//[Authorize(AuthenticationSchemes = SignatureAuthenticationDefaults.AuthenticationScheme)] +//public class DemoOpenApi : IDynamicApiController +//{ +// private readonly UserManager _userManager; + +// public DemoOpenApi(UserManager userManager) +// { +// _userManager = userManager; +// } + +// [HttpGet("helloWord")] +// public Task HelloWord() +// { +// return Task.FromResult($"Hello word. {_userManager.Account}"); +// } +//} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs new file mode 100644 index 00000000..ee4309cb --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs @@ -0,0 +1,313 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core.Service; +using Furion.DataEncryption; +using Furion.DataValidation; +using Lazy.Captcha.Core; +using Microsoft.AspNetCore.Http; +using Yitter.IdGenerator; + +namespace Admin.NET.Application.Service.App; + +/// +/// 移动应用服务 +/// +[ApiDescriptionSettings(ApplicationConst.GroupName, Order = 500)] +public class AppAuthService : IDynamicApiController, ITransient +{ + private readonly AppUserManager _appUserManager; + private readonly SqlSugarRepository _sysUserRep; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly SysMenuService _sysMenuService; + private readonly SysOnlineUserService _sysOnlineUserService; + private readonly SysConfigService _sysConfigService; + private readonly ICaptcha _captcha; + private readonly SysCacheService _sysCacheService; + + public AppAuthService(AppUserManager appUserManager, + SqlSugarRepository sysUserRep, + IHttpContextAccessor httpContextAccessor, + SysMenuService sysMenuService, + SysOnlineUserService sysOnlineUserService, + SysConfigService sysConfigService, + ICaptcha captcha, + SysCacheService sysCacheService) + { + _appUserManager = appUserManager; + _sysUserRep = sysUserRep; + _httpContextAccessor = httpContextAccessor; + _sysMenuService = sysMenuService; + _sysOnlineUserService = sysOnlineUserService; + _sysConfigService = sysConfigService; + _captcha = captcha; + _sysCacheService = sysCacheService; + } + + /// + /// 账号密码登录 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("账号密码登录")] + public virtual async Task Login([Required] LoginInput input) + { + // 判断密码错误次数(默认5次,缓存30分钟) + var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{input.Account}"; + var errorPasswordCount = _sysCacheService.Get(keyErrorPasswordCount); + if (errorPasswordCount >= 5) + throw Oops.Oh(ErrorCodeEnum.D1027); + + // 是否开启验证码 + if (await _sysConfigService.GetConfigValue(CommonConst.SysCaptcha)) + { + // 判断验证码 + if (!_captcha.Validate(input.CodeId.ToString(), input.Code)) + throw Oops.Oh(ErrorCodeEnum.D0008); + } + + // 账号是否存在 + var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Account.Equals(input.Account)); + _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + // 账号是否被冻结 + if (user.Status == StatusEnum.Disable) + throw Oops.Oh(ErrorCodeEnum.D1017); + + // 租户是否被禁用 + var tenant = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.TenantId); + if (tenant != null && tenant.Status == StatusEnum.Disable) + throw Oops.Oh(ErrorCodeEnum.Z1003); + + // 国密SM2解密(前端密码传输SM2加密后的) + try + { + input.Password = CryptogramUtil.SM2Decrypt(input.Password); + } + catch + { + throw Oops.Oh(ErrorCodeEnum.D0010); + } + + VerifyPassword(input, keyErrorPasswordCount, errorPasswordCount, user); + + // 登录成功则清空密码错误次数 + _sysCacheService.Remove(keyErrorPasswordCount); + + return await CreateToken(user); + } + + /// + /// 验证用户密码 + /// + /// + /// + /// + /// + private void VerifyPassword(LoginInput input, string keyErrorPasswordCount, int errorPasswordCount, SysUser user) + { + if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + { + if (!user.Password.Equals(MD5Encryption.Encrypt(input.Password))) + { + _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); + throw Oops.Oh(ErrorCodeEnum.D1000); + } + } + else + { + if (!CryptogramUtil.Decrypt(user.Password).Equals(input.Password)) + { + _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); + throw Oops.Oh(ErrorCodeEnum.D1000); + } + } + } + + /// + /// 手机号登录 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("手机号登录")] + public virtual async Task LoginPhone([Required] LoginPhoneInput input) + { + var verifyCode = _sysCacheService.Get($"{CacheConst.KeyPhoneVerCode}{input.Phone}"); + if (string.IsNullOrWhiteSpace(verifyCode)) + throw Oops.Oh("验证码不存在或已失效,请重新获取!"); + if (verifyCode != input.Code) + throw Oops.Oh("验证码错误!"); + + // 账号是否存在 + var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone)); + _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + return await CreateToken(user); + } + + /// + /// 生成Token令牌 🔖 + /// + /// + /// + [NonAction] + public virtual async Task CreateToken(SysUser user) + { + // 单用户登录 + await _sysOnlineUserService.SingleLogin(user.Id); + + // 生成Token令牌 + var tokenExpire = await _sysConfigService.GetTokenExpire(); + var accessToken = JWTEncryption.Encrypt(new Dictionary + { + { AppClaimConst.UserId, user.Id }, + { AppClaimConst.TenantId, user.TenantId }, + { AppClaimConst.Account, user.Account }, + { AppClaimConst.RealName, user.RealName }, + { AppClaimConst.AccountType, user.AccountType }, + { AppClaimConst.OrgId, user.OrgId }, + { AppClaimConst.OrgName, user.SysOrg?.Name }, + { AppClaimConst.OrgType, user.SysOrg?.Type }, + }, tokenExpire); + + // 生成刷新Token令牌 + var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire(); + var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire); + + // 设置响应报文头 + _httpContextAccessor.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken); + + // Swagger Knife4UI-AfterScript登录脚本 + // ke.global.setAllHeader('Authorization', 'Bearer ' + ke.response.headers['access-token']); + + return new LoginOutput + { + AccessToken = accessToken, + RefreshToken = refreshToken + }; + } + + /// + /// 获取登录账号 🔖 + /// + /// + [DisplayName("获取登录账号")] + public virtual async Task GetUserInfo() + { + var user = await _sysUserRep.GetFirstAsync(u => u.Id == _appUserManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D1011).StatusCode(401); + // 获取机构 + var org = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.OrgId); + // 获取职位 + var pos = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.PosId); + // 获取按钮集合 + var buttons = await _sysMenuService.GetOwnBtnPermList(); + // 获取角色集合 + var roleIds = await _sysUserRep.ChangeRepository>().AsQueryable() + .Where(u => u.UserId == user.Id).Select(u => u.RoleId).ToListAsync(); + + return new LoginUserOutput + { + Id = user.Id, + Account = user.Account, + RealName = user.RealName, + Phone = user.Phone, + IdCardNum = user.IdCardNum, + Email = user.Email, + AccountType = user.AccountType, + Avatar = user.Avatar, + Address = user.Address, + Signature = user.Signature, + OrgId = user.OrgId, + OrgName = org?.Name, + OrgType = org?.Type, + PosName = pos?.Name, + Buttons = buttons, + RoleIds = roleIds + }; + } + + /// + /// 获取刷新Token 🔖 + /// + /// + /// + [DisplayName("获取刷新Token")] + public virtual string GetRefreshToken([FromQuery] string accessToken) + { + var refreshTokenExpire = _sysConfigService.GetRefreshTokenExpire().GetAwaiter().GetResult(); + return JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire); + } + + /// + /// 退出系统 🔖 + /// + [DisplayName("退出系统")] + public void Logout() + { + if (string.IsNullOrWhiteSpace(_appUserManager.Account)) + throw Oops.Oh(ErrorCodeEnum.D1011); + + _httpContextAccessor.HttpContext.SignoutToSwagger(); + } + + /// + /// 获取验证码 🔖 + /// + /// + [AllowAnonymous] + [SuppressMonitor] + [DisplayName("获取验证码")] + public dynamic GetCaptcha() + { + var codeId = YitIdHelper.NextId().ToString(); + var captcha = _captcha.Generate(codeId); + return new { Id = codeId, Img = captcha.Base64 }; + } + + /// + /// 修改用户密码 + /// + /// + /// + [DisplayName("修改用户密码")] + public async Task ChangePwd(ChangePwdInput input) + { + // 国密SM2解密(前端密码传输SM2加密后的) + input.PasswordOld = CryptogramUtil.SM2Decrypt(input.PasswordOld); + input.PasswordNew = CryptogramUtil.SM2Decrypt(input.PasswordNew); + + var user = await _sysUserRep.GetFirstAsync(u => u.Id == _appUserManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + { + if (user.Password != MD5Encryption.Encrypt(input.PasswordOld)) + throw Oops.Oh(ErrorCodeEnum.D1004); + } + else + { + if (CryptogramUtil.Decrypt(user.Password) != input.PasswordOld) + throw Oops.Oh(ErrorCodeEnum.D1004); + } + + if (input.PasswordOld == input.PasswordNew) + throw Oops.Oh(ErrorCodeEnum.D1028); + + // 验证密码强度 + if (CryptogramUtil.StrongPassword) + { + user.Password = input.PasswordNew.TryValidate(CryptogramUtil.PasswordStrengthValidation) + ? CryptogramUtil.Encrypt(input.PasswordNew) + : throw Oops.Oh(CryptogramUtil.PasswordStrengthValidationMsg); + } + else + { + user.Password = CryptogramUtil.Encrypt(input.PasswordNew); + } + + return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Service/App/Auth/AppUserManager.cs b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppUserManager.cs new file mode 100644 index 00000000..9d091059 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppUserManager.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Http; + +namespace Admin.NET.Application.Service.App; + +public class AppUserManager : UserManager +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public AppUserManager(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + // 扩展属性 + +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Application/Startup.cs b/Admin.NET/Admin.NET.Application/Startup.cs new file mode 100644 index 00000000..29aa3182 --- /dev/null +++ b/Admin.NET/Admin.NET.Application/Startup.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; + +namespace Admin.NET.Application; + +[AppStartup(100)] +public class Startup : AppStartup +{ + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj new file mode 100644 index 00000000..583bdf82 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj @@ -0,0 +1,65 @@ + + + + net6.0;net8.0 + 1701;1702;1591;8632 + + enable + true + disable + True + Admin.NET + Admin.NET 通用权限开发平台 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Admin.NET/Admin.NET.Core/Attribute/AppSeedAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/AppSeedAttribute.cs new file mode 100644 index 00000000..869bbf22 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/AppSeedAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 业务应用种子特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class AppSeedAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/ConstAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/ConstAttribute.cs new file mode 100644 index 00000000..baf5a609 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/ConstAttribute.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 常量特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class ConstAttribute : Attribute +{ + public string Name { get; set; } + + public ConstAttribute(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs new file mode 100644 index 00000000..05434228 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 自定义规范化结果特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class CustomUnifyResultAttribute : Attribute +{ + public string Name { get; set; } + + public CustomUnifyResultAttribute(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs new file mode 100644 index 00000000..d9664b62 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IdempotentAttribute.cs @@ -0,0 +1,105 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Security.Claims; + +namespace Admin.NET.Core; + +/// +/// 防止重复请求过滤器特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class IdempotentAttribute : Attribute, IAsyncActionFilter +{ + /// + /// 请求间隔时间/秒 + /// + public int IntervalTime { get; set; } = 5; + + /// + /// 错误提示内容 + /// + public string Message { get; set; } = "你操作频率过快,请稍后重试!"; + + /// + /// 缓存前缀: Key+请求路由+用户Id+请求参数 + /// + public string CacheKey { get; set; } + + /// + /// 是否直接抛出异常:Ture是,False返回上次请求结果 + /// + public bool ThrowBah { get; set; } + + public IdempotentAttribute() + { + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var httpContext = context.HttpContext; + var path = httpContext.Request.Path.Value.ToString(); + var userId = httpContext.User?.FindFirstValue(ClaimConst.UserId); + var cacheExpireTime = TimeSpan.FromSeconds(IntervalTime); + + var parameters = ""; + foreach (var parameter in context.ActionDescriptor.Parameters) + { + parameters += parameter.Name; + parameters += context.ActionArguments[parameter.Name].ToJson(); + } + + var cacheKey = MD5Encryption.Encrypt($"{CacheKey}{path}{userId}{parameters}"); + var sysCacheService = App.GetRequiredService(); + if (sysCacheService.ExistKey(cacheKey)) + { + if (ThrowBah) throw Oops.Oh(Message); + + try + { + var cachedResult = sysCacheService.Get(cacheKey); + context.Result = new ObjectResult(cachedResult.Value); + } + catch (Exception ex) + { + throw Oops.Oh($"{Message}-{ex}"); + } + } + else + { + // 先加入一个空缓存,防止第一次请求结果没回来导致连续请求 + sysCacheService.Set(cacheKey, "", cacheExpireTime); + var resultContext = await next(); + if (resultContext.Result is ObjectResult objectResult) + { + var valueType = objectResult.Value.GetType(); + var responseData = new ResponseData + { + Type = valueType.Name, + Value = objectResult.Value + }; + sysCacheService.Set(cacheKey, responseData, cacheExpireTime); + } + } + } + + /// + /// 请求结果数据 + /// + private class ResponseData + { + /// + /// 结果类型 + /// + public string Type { get; set; } + + /// + /// 请求结果 + /// + public dynamic Value { get; set; } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IgnoreTableAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IgnoreTableAttribute.cs new file mode 100644 index 00000000..5fb65b99 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IgnoreTableAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 忽略表结构初始化特性(标记在实体) +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class IgnoreTableAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedAttribute.cs new file mode 100644 index 00000000..9ea18dab --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 忽略更新种子特性(标记在种子类) +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class IgnoreUpdateSeedAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedColumnAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedColumnAttribute.cs new file mode 100644 index 00000000..1bccc81d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IgnoreUpdateSeedColumnAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 忽略更新种子列特性(标记在实体属性) +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] +public class IgnoreUpdateSeedColumnAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/ImportDictAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/ImportDictAttribute.cs new file mode 100644 index 00000000..552df72f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/ImportDictAttribute.cs @@ -0,0 +1,24 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 属性字典配置 +/// +[AttributeUsage(AttributeTargets.Property)] +public class ImportDictAttribute : Attribute +{ + /// + /// 字典Code + /// + public string TypeCode { get; set; } + + /// + /// 目标对象名称 + /// + public string TargetPropName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IncreSeedAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IncreSeedAttribute.cs new file mode 100644 index 00000000..9ab1f027 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IncreSeedAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 增量种子特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class IncreSeedAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/IncreTableAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IncreTableAttribute.cs new file mode 100644 index 00000000..31f31b76 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IncreTableAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 增量表特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class IncreTableAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/LogTableAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/LogTableAttribute.cs new file mode 100644 index 00000000..aa07fd43 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/LogTableAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 日志表特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class LogTableAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/MaskNewtonsoftJsonConverter.cs b/Admin.NET/Admin.NET.Core/Attribute/MaskNewtonsoftJsonConverter.cs new file mode 100644 index 00000000..a09c801d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/MaskNewtonsoftJsonConverter.cs @@ -0,0 +1,60 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; + +namespace Admin.NET.Core; + +/// +/// 字符串掩码 +/// +[SuppressSniffer] +public class MaskNewtonsoftJsonConverter : JsonConverter +{ + public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return reader.Value.ToString(); + } + + public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString().Mask()); + } +} + +/// +/// 身份证掩码 +/// +[SuppressSniffer] +public class MaskIdCardNewtonsoftJsonConverter : JsonConverter +{ + public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return reader.Value.ToString(); + } + + public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString().MaskIdCard()); + } +} + +/// +/// 邮箱掩码 +/// +[SuppressSniffer] +public class MaskEmailNewtonsoftJsonConverter : JsonConverter +{ + public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return reader.Value.ToString(); + } + + public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString().MaskEmail()); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/MaskSystemTextJsonConverter.cs b/Admin.NET/Admin.NET.Core/Attribute/MaskSystemTextJsonConverter.cs new file mode 100644 index 00000000..6b1a12fb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/MaskSystemTextJsonConverter.cs @@ -0,0 +1,61 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Admin.NET.Core; + +/// +/// 字符串掩码 +/// +[SuppressSniffer] +public class MaskSystemTextJsonConverter : JsonConverter +{ + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString(); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString().Mask()); + } +} + +/// +/// 身份证掩码 +/// +[SuppressSniffer] +public class MaskIdCardSystemTextJsonConverter : JsonConverter +{ + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString(); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString().MaskIdCard()); + } +} + +/// +/// 邮箱掩码 +/// +[SuppressSniffer] +public class MaskEmailSystemTextJsonConverter : JsonConverter +{ + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString(); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString().MaskEmail()); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/MaxValueAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/MaxValueAttribute.cs new file mode 100644 index 00000000..81328901 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/MaxValueAttribute.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 最大值校验 +/// +[SuppressSniffer] +public class MaxValueAttribute : ValidationAttribute +{ + private double MaxValue { get; } + + /// + /// 最大值 + /// + /// + public MaxValueAttribute(double value) => this.MaxValue = value; + + /// + /// 最大值校验 + /// + /// + /// + public override bool IsValid(object value) + { + return value == null || Convert.ToDouble(value) <= this.MaxValue; + } + + /// + /// 错误信息 + /// + /// + /// + public override string FormatErrorMessage(string name) => base.FormatErrorMessage(name); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/MinValueAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/MinValueAttribute.cs new file mode 100644 index 00000000..40c73786 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/MinValueAttribute.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 最小值校验 +/// +[SuppressSniffer] +public class MinValueAttribute : ValidationAttribute +{ + private double MinValue { get; set; } + + /// + /// 最小值 + /// + /// + public MinValueAttribute(double value) => this.MinValue = value; + + /// + /// 最小值校验 + /// + /// + /// + public override bool IsValid(object value) + { + return value == null || Convert.ToDouble(value) > this.MinValue; + } + + /// + /// 错误信息 + /// + /// + /// + public override string FormatErrorMessage(string name) => base.FormatErrorMessage(name); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/SysTableAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/SysTableAttribute.cs new file mode 100644 index 00000000..f526b42c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/SysTableAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统表特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class SysTableAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Cache/CacheSetup.cs b/Admin.NET/Admin.NET.Core/Cache/CacheSetup.cs new file mode 100644 index 00000000..6281765b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Cache/CacheSetup.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class CacheSetup +{ + /// + /// 缓存注册(新生命Redis组件) + /// + /// + public static void AddCache(this IServiceCollection services) + { + ICache cache = Cache.Default; + + var cacheOptions = App.GetConfig("Cache", true); + if (cacheOptions.CacheType == CacheTypeEnum.Redis.ToString()) + { + cache = new FullRedis(new RedisOptions + { + Configuration = cacheOptions.Redis.Configuration, + Prefix = cacheOptions.Redis.Prefix + }); + if (cacheOptions.Redis.MaxMessageSize > 0) + ((FullRedis)cache).MaxMessageSize = cacheOptions.Redis.MaxMessageSize; + } + + services.AddSingleton(cache); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Cache/SqlSugarCache.cs b/Admin.NET/Admin.NET.Core/Cache/SqlSugarCache.cs new file mode 100644 index 00000000..6bbcda64 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Cache/SqlSugarCache.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// SqlSugar二级缓存 +/// +public class SqlSugarCache : ICacheService +{ + /// + /// 系统缓存服务 + /// + private static readonly SysCacheService _cache = App.GetRequiredService(); + + public void Add(string key, V value) + { + _cache.Set($"{CacheConst.SqlSugar}{key}", value); + } + + public void Add(string key, V value, int cacheDurationInSeconds) + { + _cache.Set($"{CacheConst.SqlSugar}{key}", value, TimeSpan.FromSeconds(cacheDurationInSeconds)); + } + + public bool ContainsKey(string key) + { + return _cache.ExistKey($"{CacheConst.SqlSugar}{key}"); + } + + public V Get(string key) + { + return _cache.Get($"{CacheConst.SqlSugar}{key}"); + } + + public IEnumerable GetAllKey() + { + return _cache.GetKeysByPrefixKey(CacheConst.SqlSugar); + } + + public V GetOrCreate(string key, Func create, int cacheDurationInSeconds = int.MaxValue) + { + return _cache.GetOrAdd($"{CacheConst.SqlSugar}{key}", (cacheKey) => + { + return create(); + }, cacheDurationInSeconds); + } + + public void Remove(string key) + { + _cache.Remove(key); // SqlSugar调用Remove方法时,key中已包含了CacheConst.SqlSugar前缀 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs new file mode 100644 index 00000000..d3ae76e3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs @@ -0,0 +1,93 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 缓存相关常量 +/// +public class CacheConst +{ + /// + /// 用户权限缓存(按钮集合) + /// + public const string KeyUserButton = "sys_user_button:"; + + /// + /// 用户接口缓存(接口集合) + /// + public const string KeyUserApi = "sys_user_api:"; + + /// + /// 用户机构缓存 + /// + public const string KeyUserOrg = "sys_user_org:"; + + /// + /// 角色最大数据范围缓存 + /// + public const string KeyRoleMaxDataScope = "sys_role_maxDataScope:"; + + /// + /// 在线用户缓存 + /// + public const string KeyUserOnline = "sys_user_online:"; + + /// + /// 图形验证码缓存 + /// + public const string KeyVerCode = "sys_verCode:"; + + /// + /// 手机验证码缓存 + /// + public const string KeyPhoneVerCode = "sys_phoneVerCode:"; + + /// + /// 密码错误次数缓存 + /// + public const string KeyErrorPasswordCount = "sys_errorPasswordCount:"; + + /// + /// 租户缓存 + /// + public const string KeyTenant = "sys_tenant"; + + /// + /// 常量下拉框 + /// + public const string KeyConst = "sys_const:"; + + /// + /// 所有缓存关键字集合 + /// + public const string KeyAll = "sys_keys"; + + /// + /// SqlSugar二级缓存 + /// + public const string SqlSugar = "sys_sqlSugar:"; + + /// + /// 开放接口身份缓存 + /// + public const string KeyOpenAccess = "sys_open_access:"; + + /// + /// 开放接口身份随机数缓存 + /// + public const string KeyOpenAccessNonce = "sys_open_access_nonce:"; + + /// + /// 登录黑名单 + /// + public const string KeyBlacklist = "sys_blacklist:"; + + /// + /// 系统配置缓存 + /// + public const string KeyConfig = "sys_config:"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/ClaimConst.cs b/Admin.NET/Admin.NET.Core/Const/ClaimConst.cs new file mode 100644 index 00000000..7fdf85f3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Const/ClaimConst.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// Claim相关常量 +/// +public class ClaimConst +{ + /// + /// 用户Id + /// + public const string UserId = "UserId"; + + /// + /// 账号 + /// + public const string Account = "Account"; + + /// + /// 真实姓名 + /// + public const string RealName = "RealName"; + + /// + /// 昵称 + /// + public const string NickName = "NickName"; + + /// + /// 账号类型 + /// + public const string AccountType = "AccountType"; + + /// + /// 租户Id + /// + public const string TenantId = "TenantId"; + + /// + /// 组织机构Id + /// + public const string OrgId = "OrgId"; + + /// + /// 组织机构名称 + /// + public const string OrgName = "OrgName"; + + /// + /// 组织机构类型 + /// + public const string OrgType = "OrgType"; + + /// + /// 微信OpenId + /// + public const string OpenId = "OpenId"; + + /// + /// 登录模式PC、APP + /// + public const string LoginMode = "LoginMode"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/CommonConst.cs b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs new file mode 100644 index 00000000..ca46ed81 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs @@ -0,0 +1,101 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 通用常量 +/// +[Const("平台配置")] +public class CommonConst +{ + /// + /// 演示环境开关 + /// + public const string SysDemoEnv = "sys_demo"; + + /// + /// 默认密码 + /// + public const string SysPassword = "sys_password"; + + /// + /// 登录二次验证 + /// + public const string SysSecondVer = "sys_second_ver"; + + /// + /// 开启图形验证码 + /// + public const string SysCaptcha = "sys_captcha"; + + /// + /// 开启水印 + /// + public const string SysWatermark = "sys_watermark"; + + /// + /// 开启操作日志 + /// + public const string SysOpLog = "sys_oplog"; + + /// + /// Token过期时间 + /// + public const string SysTokenExpire = "sys_token_expire"; + + /// + /// RefreshToken过期时间 + /// + public const string SysRefreshTokenExpire = "sys_refresh_token_expire"; + + /// + /// 开启发送异常日志邮件 + /// + public const string SysErrorMail = "sys_error_mail"; + + /// + /// 单用户登录 + /// + public const string SysSingleLogin = "sys_single_login"; + + /// + /// 系统管理员角色编码 + /// + public const string SysAdminRole = "sys_admin"; + + ///// + ///// 开启全局脱敏处理(默认不开启) + ///// + //public static bool SysSensitiveDetection = false; + + /// + /// 开启域登录验证 + /// + public const string SysDomainLogin = "sys_domain_login"; + + /// + /// 日志分组名称 + /// + public const string SysLogCategoryName = "System.Logging.LoggingMonitor"; + + /// + /// 事件-增加异常日志 + /// + public const string AddExLog = "Add:ExLog"; + + /// + /// 事件-发送异常邮件 + /// + public const string SendErrorMail = "Send:ErrorMail"; + + /// + /// 基础接口资源列表 + /// + + public static readonly List SysBaseRoutes = new() { "sysAuth/login", "sysAuth/unLockScreen", "sysAuth/loginPhone", "sysAuth/userInfo", "sysAuth/refreshToken", "sysAuth/logout", "sysAuth/loginConfig", "sysAuth/watermarkConfig", "sysAuth/captcha", "sysMenu/loginMenuTree", "sysOAuth/signIn", "sysOAuth/signInCallback", "sysOnlineUser/page", "sysOrg/list", "sysPos/list", "sysRole/page", "sysRole/list", + "sysFile/uploadAvatar", "sysFile/uploadSignature", "sysUser/baseInfo", "sysUser/baseInfo", "sysUser/changePwd", "sysNotice/page", "sysNotice/add", "sysNotice/update", "sysNotice/delete", "sysNotice/public", "sysNotice/setRead", "sysNotice/pageReceived", "sysNotice/unReadList" }; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs b/Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs new file mode 100644 index 00000000..ddbcbd43 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// SqlSugar相关常量 +/// +public class SqlSugarConst +{ + /// + /// 默认主数据库标识(默认租户) + /// + public const string MainConfigId = "1300000000001"; + + /// + /// 默认日志数据库标识 + /// + public const string LogConfigId = "1300000000002"; + + /// + /// 默认表主键 + /// + public const string PrimaryKey = "Id"; + + /// + /// 默认租户Id + /// + public const long DefaultTenantId = 1300000000001; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs b/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs new file mode 100644 index 00000000..d000be13 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs @@ -0,0 +1,146 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 框架实体基类Id +/// +public abstract class EntityBaseId +{ + /// + /// 雪花Id + /// + [SugarColumn(ColumnName = "Id", ColumnDescription = "主键Id", IsPrimaryKey = true, IsIdentity = false)] + public virtual long Id { get; set; } +} + +/// +/// 框架实体基类 +/// +[SugarIndex("index_{table}_CT", nameof(CreateTime), OrderByType.Asc)] +public abstract class EntityBase : EntityBaseId, IDeletedFilter +{ + /// + /// 创建时间 + /// + [SugarColumn(ColumnDescription = "创建时间", IsNullable = true, IsOnlyIgnoreUpdate = true, InsertServerTime = true)] + public virtual DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnDescription = "更新时间", IsOnlyIgnoreInsert = true, UpdateServerTime = true)] + public virtual DateTime? UpdateTime { get; set; } + + /// + /// 创建者Id + /// + [SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true)] + public virtual long? CreateUserId { get; set; } + + ///// + ///// 创建者 + ///// + //[Newtonsoft.Json.JsonIgnore] + //[System.Text.Json.Serialization.JsonIgnore] + //[Navigate(NavigateType.OneToOne, nameof(CreateUserId))] + //public virtual SysUser CreateUser { get; set; } + + /// + /// 创建者姓名 + /// + [SugarColumn(ColumnDescription = "创建者姓名", Length = 64, IsOnlyIgnoreUpdate = true)] + public virtual string? CreateUserName { get; set; } + + /// + /// 修改者Id + /// + [SugarColumn(ColumnDescription = "修改者Id")] + public virtual long? UpdateUserId { get; set; } + + ///// + ///// 修改者 + ///// + //[Newtonsoft.Json.JsonIgnore] + //[System.Text.Json.Serialization.JsonIgnore] + //[Navigate(NavigateType.OneToOne, nameof(UpdateUserId))] + //public virtual SysUser UpdateUser { get; set; } + + /// + /// 修改者姓名 + /// + [SugarColumn(ColumnDescription = "修改者姓名", Length = 64)] + public virtual string? UpdateUserName { get; set; } + + /// + /// 软删除 + /// + [SugarColumn(ColumnDescription = "软删除")] + public virtual bool IsDelete { get; set; } = false; +} + +/// +/// 业务数据实体基类(数据权限) +/// +public abstract class EntityBaseData : EntityBase, IOrgIdFilter +{ + /// + /// 创建者部门Id + /// + [SugarColumn(ColumnDescription = "创建者部门Id", IsOnlyIgnoreUpdate = true)] + public virtual long? CreateOrgId { get; set; } + + /// + /// 创建者部门 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(CreateOrgId))] + public virtual SysOrg CreateOrg { get; set; } + + /// + /// 创建者部门名称 + /// + [SugarColumn(ColumnDescription = "创建者部门名称", Length = 64, IsOnlyIgnoreUpdate = true)] + public virtual string? CreateOrgName { get; set; } +} + +/// +/// 租户实体基类 +/// +public abstract class EntityTenant : EntityBase, ITenantIdFilter +{ + /// + /// 租户Id + /// + [SugarColumn(ColumnDescription = "租户Id", IsOnlyIgnoreUpdate = true)] + public virtual long? TenantId { get; set; } +} + +/// +/// 租户实体基类Id +/// +public abstract class EntityTenantId : EntityBaseId, ITenantIdFilter +{ + /// + /// 租户Id + /// + [SugarColumn(ColumnDescription = "租户Id", IsOnlyIgnoreUpdate = true)] + public virtual long? TenantId { get; set; } +} + +/// +/// 租户实体基类 + 业务数据(数据权限) +/// +public abstract class EntityTenantBaseData : EntityBaseData, ITenantIdFilter +{ + /// + /// 租户Id + /// + [SugarColumn(ColumnDescription = "租户Id", IsOnlyIgnoreUpdate = true)] + public virtual long? TenantId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/IEntityFilter.cs b/Admin.NET/Admin.NET.Core/Entity/IEntityFilter.cs new file mode 100644 index 00000000..05c8727d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/IEntityFilter.cs @@ -0,0 +1,40 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 假删除接口过滤器 +/// +internal interface IDeletedFilter +{ + /// + /// 软删除 + /// + bool IsDelete { get; set; } +} + +/// +/// 租户Id接口过滤器 +/// +internal interface ITenantIdFilter +{ + /// + /// 租户Id + /// + long? TenantId { get; set; } +} + +/// +/// 机构Id接口过滤器 +/// +internal interface IOrgIdFilter +{ + /// + /// 创建者部门Id + /// + long? CreateOrgId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs new file mode 100644 index 00000000..ba23d162 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs @@ -0,0 +1,125 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 代码生成表 +/// +[SugarTable(null, "代码生成表")] +[SysTable] +[SugarIndex("index_{table}_B", nameof(BusName), OrderByType.Asc)] +[SugarIndex("index_{table}_T", nameof(TableName), OrderByType.Asc)] +public partial class SysCodeGen : EntityBase +{ + /// + /// 作者姓名 + /// + [SugarColumn(ColumnDescription = "作者姓名", Length = 32)] + [MaxLength(32)] + public string? AuthorName { get; set; } + + /// + /// 是否移除表前缀 + /// + [SugarColumn(ColumnDescription = "是否移除表前缀", Length = 8)] + [MaxLength(8)] + public string? TablePrefix { get; set; } + + /// + /// 生成方式 + /// + [SugarColumn(ColumnDescription = "生成方式", Length = 32)] + [MaxLength(32)] + public string? GenerateType { get; set; } + + /// + /// 库定位器名 + /// + [SugarColumn(ColumnDescription = "库定位器名", Length = 64)] + [MaxLength(64)] + public string? ConfigId { get; set; } + + /// + /// 数据库名(保留字段) + /// + [SugarColumn(ColumnDescription = "数据库库名", Length = 64)] + [MaxLength(64)] + public string? DbName { get; set; } + + /// + /// 数据库类型 + /// + [SugarColumn(ColumnDescription = "数据库类型", Length = 64)] + [MaxLength(64)] + public string? DbType { get; set; } + + /// + /// 数据库链接 + /// + [SugarColumn(ColumnDescription = "数据库链接", Length = 256)] + [MaxLength(256)] + public string? ConnectionString { get; set; } + + /// + /// 数据库表名 + /// + [SugarColumn(ColumnDescription = "数据库表名", Length = 128)] + [MaxLength(128)] + public string? TableName { get; set; } + + /// + /// 命名空间 + /// + [SugarColumn(ColumnDescription = "命名空间", Length = 128)] + [MaxLength(128)] + public string? NameSpace { get; set; } + + /// + /// 业务名 + /// + [SugarColumn(ColumnDescription = "业务名", Length = 128)] + [MaxLength(128)] + public string? BusName { get; set; } + + /// + /// 是否生成菜单 + /// + [SugarColumn(ColumnDescription = "是否生成菜单")] + public bool GenerateMenu { get; set; } = true; + + /// + /// 菜单图标 + /// + [SugarColumn(ColumnDescription = "菜单图标", Length = 32)] + public string? MenuIcon { get; set; } = "ele-Menu"; + + /// + /// 菜单编码 + /// + [SugarColumn(ColumnDescription = "菜单编码")] + public long? MenuPid { get; set; } + + /// + /// 页面目录 + /// + [SugarColumn(ColumnDescription = "页面目录", Length = 32)] + public string? PagePath { get; set; } + + /// + /// 支持打印类型 + /// + [SugarColumn(ColumnDescription = "支持打印类型", Length = 32)] + [MaxLength(32)] + public string? PrintType { get; set; } + + /// + /// 打印模版名称 + /// + [SugarColumn(ColumnDescription = "打印模版名称", Length = 32)] + [MaxLength(32)] + public string? PrintName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs new file mode 100644 index 00000000..03f6b814 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs @@ -0,0 +1,193 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 代码生成字段配置表 +/// +[SugarTable(null, "代码生成字段配置表")] +[SysTable] +public partial class SysCodeGenConfig : EntityBase +{ + /// + /// 代码生成主表Id + /// + [SugarColumn(ColumnDescription = "主表Id")] + public long CodeGenId { get; set; } + + /// + /// 数据库字段名 + /// + [SugarColumn(ColumnDescription = "字段名称", Length = 128)] + [Required, MaxLength(128)] + public virtual string ColumnName { get; set; } + + /// + /// 实体属性名 + /// + [SugarColumn(ColumnDescription = "属性名称", Length = 128)] + [Required, MaxLength(128)] + public virtual string PropertyName { get; set; } + + /// + /// 字段数据长度 + /// + [SugarColumn(ColumnDescription = "字段数据长度", DefaultValue = "0")] + public int ColumnLength { get; set; } + + /// + /// 字段描述 + /// + [SugarColumn(ColumnDescription = "字段描述", Length = 128)] + [MaxLength(128)] + public string? ColumnComment { get; set; } + + /// + /// .NET数据类型 + /// + [SugarColumn(ColumnDescription = "NET数据类型", Length = 64)] + [MaxLength(64)] + public string? NetType { get; set; } + + /// + /// 作用类型(字典) + /// + [SugarColumn(ColumnDescription = "作用类型", Length = 64)] + [MaxLength(64)] + public string? EffectType { get; set; } + + /// + /// 外键实体名称 + /// + [SugarColumn(ColumnDescription = "外键实体名称", Length = 64)] + [MaxLength(64)] + public string? FkEntityName { get; set; } + + /// + /// 外键表名称 + /// + [SugarColumn(ColumnDescription = "外键表名称", Length = 128)] + [MaxLength(128)] + public string? FkTableName { get; set; } + + /// + /// 外键显示字段 + /// + [SugarColumn(ColumnDescription = "外键显示字段", Length = 64)] + [MaxLength(64)] + public string? FkColumnName { get; set; } + + /// + /// 外键显示字段.NET类型 + /// + [SugarColumn(ColumnDescription = "外键显示字段.NET类型", Length = 64)] + [MaxLength(64)] + public string? FkColumnNetType { get; set; } + + /// + /// 字典编码 + /// + [SugarColumn(ColumnDescription = "字典编码", Length = 64)] + [MaxLength(64)] + public string? DictTypeCode { get; set; } + + /// + /// 列表是否缩进(字典) + /// + [SugarColumn(ColumnDescription = "列表是否缩进", Length = 8)] + [MaxLength(8)] + public string? WhetherRetract { get; set; } + + /// + /// 是否必填(字典) + /// + [SugarColumn(ColumnDescription = "是否必填", Length = 8)] + [MaxLength(8)] + public string? WhetherRequired { get; set; } + + /// + /// 是否可排序(字典) + /// + [SugarColumn(ColumnDescription = "是否可排序", Length = 8)] + [MaxLength(8)] + public string? WhetherSortable { get; set; } + + /// + /// 是否是查询条件 + /// + [SugarColumn(ColumnDescription = "是否是查询条件", Length = 8)] + [MaxLength(8)] + public string? QueryWhether { get; set; } + + /// + /// 查询方式 + /// + [SugarColumn(ColumnDescription = "查询方式", Length = 16)] + [MaxLength(16)] + public string? QueryType { get; set; } + + /// + /// 列表显示 + /// + [SugarColumn(ColumnDescription = "列表显示", Length = 8)] + [MaxLength(8)] + public string? WhetherTable { get; set; } + + /// + /// 增改 + /// + [SugarColumn(ColumnDescription = "增改", Length = 8)] + [MaxLength(8)] + public string? WhetherAddUpdate { get; set; } + + /// + /// 主键 + /// + [SugarColumn(ColumnDescription = "主键", Length = 8)] + [MaxLength(8)] + public string? ColumnKey { get; set; } + + /// + /// 数据库中类型(物理类型) + /// + [SugarColumn(ColumnDescription = "数据库中类型", Length = 64)] + [MaxLength(64)] + public string? DataType { get; set; } + + /// + /// 是否通用字段 + /// + [SugarColumn(ColumnDescription = "是否通用字段", Length = 8)] + [MaxLength(8)] + public string? WhetherCommon { get; set; } + + /// + /// 显示文本字段 + /// + [SugarColumn(ColumnDescription = "显示文本字段", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? DisplayColumn { get; set; } + + /// + /// 选中值字段 + /// + [SugarColumn(ColumnDescription = "选中值字段", Length = 128)] + [MaxLength(128)] + public string? ValueColumn { get; set; } + + /// + /// 父级字段 + /// + [SugarColumn(ColumnDescription = "父级字段", Length = 128)] + [MaxLength(128)] + public string? PidColumn { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysConfig.cs new file mode 100644 index 00000000..d4fb5f17 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysConfig.cs @@ -0,0 +1,65 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统参数配置表 +/// +[SugarTable(null, "系统参数配置表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysConfig : EntityBase +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 64)] + [MaxLength(64)] + public string? Code { get; set; } + + /// + /// 属性值 + /// + [SugarColumn(ColumnDescription = "属性值", Length = 64)] + [MaxLength(64)] + [IgnoreUpdateSeedColumn] + public string? Value { get; set; } + + /// + /// 是否是内置参数(Y-是,N-否) + /// + [SugarColumn(ColumnDescription = "是否是内置参数")] + public YesNoEnum SysFlag { get; set; } + + /// + /// 分组编码 + /// + [SugarColumn(ColumnDescription = "分组编码", Length = 64)] + [MaxLength(64)] + public string? GroupCode { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 256)] + [MaxLength(256)] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysDictData.cs b/Admin.NET/Admin.NET.Core/Entity/SysDictData.cs new file mode 100644 index 00000000..c8d1b6e2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysDictData.cs @@ -0,0 +1,97 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统字典值表 +/// +[SugarTable(null, "系统字典值表")] +[SysTable] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysDictData : EntityBase +{ + /// + /// 字典类型Id + /// + [SugarColumn(ColumnDescription = "字典类型Id")] + public long DictTypeId { get; set; } + + /// + /// 字典类型 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(DictTypeId))] + public SysDictType DictType { get; set; } + + /// + /// 值 + /// + [SugarColumn(ColumnDescription = "值", Length = 128)] + [Required, MaxLength(128)] + public virtual string Value { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 128)] + [Required, MaxLength(128)] + public virtual string Code { get; set; } + + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 128)] + [MaxLength(128)] + public virtual string? Name { get; set; } + + /// + /// 显示样式-标签颜色 + /// + [SugarColumn(ColumnDescription = "显示样式-标签颜色", Length = 16)] + [MaxLength(16)] + public string? TagType { get; set; } + + /// + /// 显示样式-Style(控制显示样式) + /// + [SugarColumn(ColumnDescription = "显示样式-Style", Length = 512)] + [MaxLength(512)] + public string? StyleSetting { get; set; } + + /// + /// 显示样式-Class(控制显示样式) + /// + [SugarColumn(ColumnDescription = "显示样式-Class", Length = 512)] + [MaxLength(512)] + public string? ClassSetting { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 2048)] + [MaxLength(2048)] + public string? Remark { get; set; } + + /// + /// 拓展数据(保存业务功能的配置项) + /// + [SugarColumn(ColumnDescription = "拓展数据(保存业务功能的配置项)", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? ExtData { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysDictType.cs b/Admin.NET/Admin.NET.Core/Entity/SysDictType.cs new file mode 100644 index 00000000..df1d3d2a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysDictType.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统字典类型表 +/// +[SugarTable(null, "系统字典类型表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysDictType : EntityBase +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 64)] + [Required, MaxLength(64)] + public virtual string Code { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 256)] + [MaxLength(256)] + public string? Remark { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 字典值集合 + /// + [Navigate(NavigateType.OneToMany, nameof(SysDictData.DictTypeId))] + public List Children { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysFile.cs b/Admin.NET/Admin.NET.Core/Entity/SysFile.cs new file mode 100644 index 00000000..ccb1c103 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysFile.cs @@ -0,0 +1,104 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统文件表 +/// +[SugarTable(null, "系统文件表")] +[SysTable] +[SugarIndex("index_{table}_F", nameof(FileName), OrderByType.Asc)] +public partial class SysFile : EntityBase +{ + /// + /// 提供者 + /// + [SugarColumn(ColumnDescription = "提供者", Length = 128)] + [MaxLength(128)] + public string? Provider { get; set; } + + /// + /// 仓储名称 + /// + [SugarColumn(ColumnDescription = "仓储名称", Length = 128)] + [MaxLength(128)] + public string? BucketName { get; set; } + + /// + /// 文件名称(源文件名) + /// + [SugarColumn(ColumnDescription = "文件名称", Length = 128)] + [MaxLength(128)] + public string? FileName { get; set; } + + /// + /// 文件后缀 + /// + [SugarColumn(ColumnDescription = "文件后缀", Length = 16)] + [MaxLength(16)] + public string? Suffix { get; set; } + + /// + /// 存储路径 + /// + [SugarColumn(ColumnDescription = "存储路径", Length = 512)] + [MaxLength(512)] + public string? FilePath { get; set; } + + /// + /// 文件大小KB + /// + [SugarColumn(ColumnDescription = "文件大小KB")] + public long SizeKb { get; set; } + + /// + /// 文件大小信息-计算后的 + /// + [SugarColumn(ColumnDescription = "文件大小信息", Length = 64)] + [MaxLength(64)] + public string? SizeInfo { get; set; } + + /// + /// 外链地址-OSS上传后生成外链地址方便前端预览 + /// + [SugarColumn(ColumnDescription = "外链地址", Length = 512)] + [MaxLength(512)] + public string? Url { get; set; } + + /// + /// 文件MD5 + /// + [SugarColumn(ColumnDescription = "文件MD5", Length = 128)] + [MaxLength(128)] + public string? FileMd5 { get; set; } + + /// + /// 关联对象名称(如子对象) + /// + [SugarColumn(ColumnDescription = "关联对象名称", Length = 128)] + [MaxLength(128)] + public string? RelationName { get; set; } + + /// + /// 关联对象Id + /// + [SugarColumn(ColumnDescription = "关联对象Id")] + public long? RelationId { get; set; } + + /// + /// 所属Id(如主对象) + /// + [SugarColumn(ColumnDescription = "所属Id")] + public long? BelongId { get; set; } + + /// + /// 文件类别 + /// + [SugarColumn(ColumnDescription = "文件类别", Length = 128)] + [MaxLength(128)] + public string? FileType { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysJobCluster.cs b/Admin.NET/Admin.NET.Core/Entity/SysJobCluster.cs new file mode 100644 index 00000000..6687cf6d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysJobCluster.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统作业集群表 +/// +[SugarTable(null, "系统作业集群表")] +[SysTable] +public partial class SysJobCluster : EntityBaseId +{ + /// + /// 作业集群Id + /// + [SugarColumn(ColumnDescription = "作业集群Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string ClusterId { get; set; } + + /// + /// 描述信息 + /// + [SugarColumn(ColumnDescription = "描述信息", Length = 128)] + [MaxLength(128)] + public string? Description { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public ClusterStatus Status { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnDescription = "更新时间")] + public DateTime? UpdatedTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysJobDetail.cs b/Admin.NET/Admin.NET.Core/Entity/SysJobDetail.cs new file mode 100644 index 00000000..3c2868f4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysJobDetail.cs @@ -0,0 +1,87 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统作业信息表 +/// +[SugarTable(null, "系统作业信息表")] +[SysTable] +[SugarIndex("index_{table}_J", nameof(JobId), OrderByType.Asc)] +public partial class SysJobDetail : EntityBaseId +{ + /// + /// 作业Id + /// + [SugarColumn(ColumnDescription = "作业Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string JobId { get; set; } + + /// + /// 组名称 + /// + [SugarColumn(ColumnDescription = "组名称", Length = 128)] + [MaxLength(128)] + public string? GroupName { get; set; } = "default"; + + /// + /// 作业类型FullName + /// + [SugarColumn(ColumnDescription = "作业类型", Length = 128)] + [MaxLength(128)] + public string? JobType { get; set; } + + /// + /// 程序集Name + /// + [SugarColumn(ColumnDescription = "程序集", Length = 128)] + [MaxLength(128)] + public string? AssemblyName { get; set; } + + /// + /// 描述信息 + /// + [SugarColumn(ColumnDescription = "描述信息", Length = 128)] + [MaxLength(128)] + public string? Description { get; set; } + + /// + /// 是否并行执行 + /// + [SugarColumn(ColumnDescription = "是否并行执行")] + public bool Concurrent { get; set; } = true; + + /// + /// 是否扫描特性触发器 + /// + [SugarColumn(ColumnDescription = "是否扫描特性触发器", ColumnName = "annotation")] + public bool IncludeAnnotation { get; set; } = false; + + /// + /// 额外数据 + /// + [SugarColumn(ColumnDescription = "额外数据", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Properties { get; set; } = "{}"; + + /// + /// 更新时间 + /// + [SugarColumn(ColumnDescription = "更新时间")] + public DateTime? UpdatedTime { get; set; } + + /// + /// 作业创建类型 + /// + [SugarColumn(ColumnDescription = "作业创建类型")] + public JobCreateTypeEnum CreateType { get; set; } = JobCreateTypeEnum.BuiltIn; + + /// + /// 脚本代码 + /// + [SugarColumn(ColumnDescription = "脚本代码", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? ScriptCode { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysJobTrigger.cs b/Admin.NET/Admin.NET.Core/Entity/SysJobTrigger.cs new file mode 100644 index 00000000..dc6d5c85 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysJobTrigger.cs @@ -0,0 +1,147 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统作业触发器表 +/// +[SugarTable(null, "系统作业触发器表")] +[SysTable] +public partial class SysJobTrigger : EntityBaseId +{ + /// + /// 触发器Id + /// + [SugarColumn(ColumnDescription = "触发器Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string TriggerId { get; set; } + + /// + /// 作业Id + /// + [SugarColumn(ColumnDescription = "作业Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string JobId { get; set; } + + /// + /// 触发器类型FullName + /// + [SugarColumn(ColumnDescription = "触发器类型", Length = 128)] + [MaxLength(128)] + public string? TriggerType { get; set; } + + /// + /// 程序集Name + /// + [SugarColumn(ColumnDescription = "程序集", Length = 128)] + [MaxLength(128)] + public string? AssemblyName { get; set; } = "Furion.Pure"; + + /// + /// 参数 + /// + [SugarColumn(ColumnDescription = "参数", Length = 128)] + [MaxLength(128)] + public string? Args { get; set; } + + /// + /// 描述信息 + /// + [SugarColumn(ColumnDescription = "描述信息", Length = 128)] + [MaxLength(128)] + public string? Description { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public TriggerStatus Status { get; set; } = TriggerStatus.Ready; + + /// + /// 起始时间 + /// + [SugarColumn(ColumnDescription = "起始时间")] + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + [SugarColumn(ColumnDescription = "结束时间")] + public DateTime? EndTime { get; set; } + + /// + /// 最近运行时间 + /// + [SugarColumn(ColumnDescription = "最近运行时间")] + public DateTime? LastRunTime { get; set; } + + /// + /// 下一次运行时间 + /// + [SugarColumn(ColumnDescription = "下一次运行时间")] + public DateTime? NextRunTime { get; set; } + + /// + /// 触发次数 + /// + [SugarColumn(ColumnDescription = "触发次数")] + public long NumberOfRuns { get; set; } + + /// + /// 最大触发次数(0:不限制,n:N次) + /// + [SugarColumn(ColumnDescription = "最大触发次数")] + public long MaxNumberOfRuns { get; set; } + + /// + /// 出错次数 + /// + [SugarColumn(ColumnDescription = "出错次数")] + public long NumberOfErrors { get; set; } + + /// + /// 最大出错次数(0:不限制,n:N次) + /// + [SugarColumn(ColumnDescription = "最大出错次数")] + public long MaxNumberOfErrors { get; set; } + + /// + /// 重试次数 + /// + [SugarColumn(ColumnDescription = "重试次数")] + public int NumRetries { get; set; } + + /// + /// 重试间隔时间(ms) + /// + [SugarColumn(ColumnDescription = "重试间隔时间(ms)")] + public int RetryTimeout { get; set; } = 1000; + + /// + /// 是否立即启动 + /// + [SugarColumn(ColumnDescription = "是否立即启动")] + public bool StartNow { get; set; } = true; + + /// + /// 是否启动时执行一次 + /// + [SugarColumn(ColumnDescription = "是否启动时执行一次")] + public bool RunOnStart { get; set; } = false; + + /// + /// 是否在启动时重置最大触发次数等于一次的作业 + /// + [SugarColumn(ColumnDescription = "是否重置触发次数")] + public bool ResetOnlyOnce { get; set; } = true; + + /// + /// 更新时间 + /// + [SugarColumn(ColumnDescription = "更新时间")] + public DateTime? UpdatedTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysJobTriggerRecord.cs b/Admin.NET/Admin.NET.Core/Entity/SysJobTriggerRecord.cs new file mode 100644 index 00000000..080c5cce --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysJobTriggerRecord.cs @@ -0,0 +1,72 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统作业触发器运行记录表 +/// +[SugarTable(null, "系统作业触发器运行记录表")] +[SysTable] +public partial class SysJobTriggerRecord : EntityBaseId +{ + /// + /// 作业Id + /// + [SugarColumn(ColumnDescription = "作业Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string JobId { get; set; } + + /// + /// 触发器Id + /// + [SugarColumn(ColumnDescription = "触发器Id", Length = 64)] + [Required, MaxLength(64)] + public virtual string TriggerId { get; set; } + + /// + /// 当前运行次数 + /// + [SugarColumn(ColumnDescription = "当前运行次数")] + public long NumberOfRuns { get; set; } + + /// + /// 最近运行时间 + /// + [SugarColumn(ColumnDescription = "最近运行时间")] + public DateTime? LastRunTime { get; set; } + + /// + /// 下一次运行时间 + /// + [SugarColumn(ColumnDescription = "下一次运行时间")] + public DateTime? NextRunTime { get; set; } + + /// + /// 触发器状态 + /// + [SugarColumn(ColumnDescription = "触发器状态")] + public TriggerStatus Status { get; set; } = TriggerStatus.Ready; + + /// + /// 本次执行结果 + /// + [SugarColumn(ColumnDescription = "本次执行结果", Length = 128)] + [MaxLength(128)] + public string? Result { get; set; } + + /// + /// 本次执行耗时 + /// + [SugarColumn(ColumnDescription = "本次执行耗时")] + public long ElapsedTime { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnDescription = "创建时间")] + public DateTime? CreatedTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLdap.cs b/Admin.NET/Admin.NET.Core/Entity/SysLdap.cs new file mode 100644 index 00000000..a8d629b7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysLdap.cs @@ -0,0 +1,89 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统域登录信息配置表 +/// +[SugarTable(null, "系统域登录信息配置表")] +[SysTable] +public class SysLdap : EntityTenant +{ + /// + /// 主机 + /// + [SugarColumn(ColumnDescription = "主机", Length = 128)] + [Required] + public virtual string Host { get; set; } + + /// + /// 端口 + /// + [SugarColumn(ColumnDescription = "端口")] + public virtual int Port { get; set; } + + /// + /// 用户搜索基准 + /// + [SugarColumn(ColumnDescription = "用户搜索基准", Length = 128)] + [Required] + public virtual string BaseDn { get; set; } + + /// + /// 绑定DN(有管理权限制的用户) + /// + [SugarColumn(ColumnDescription = "绑定DN", Length = 32)] + [Required] + public virtual string BindDn { get; set; } + + /// + /// 绑定密码(有管理权限制的用户密码) + /// + [SugarColumn(ColumnDescription = "绑定密码", Length = 512)] + [Required] + public virtual string BindPass { get; set; } + + /// + /// 用户过滤规则 + /// + [SugarColumn(ColumnDescription = "用户过滤规则", Length = 128)] + [Required] + public virtual string AuthFilter { get; set; } = "sAMAccountName=%s"; + + /// + /// Ldap版本 + /// + [SugarColumn(ColumnDescription = "Ldap版本")] + public int Version { get; set; } + + /// + /// 绑定域账号字段属性值 + /// + [SugarColumn(ColumnDescription = "绑定域账号字段属性值", Length = 32)] + [Required] + public virtual string BindAttrAccount { get; set; } = "sAMAccountName"; + + /// + /// 绑定用户EmployeeId属性值 + /// + [SugarColumn(ColumnDescription = "绑定用户EmployeeId属性值", Length = 32)] + [Required] + public virtual string BindAttrEmployeeId { get; set; } = "EmployeeId"; + + /// + /// 绑定Code属性值 + /// + [SugarColumn(ColumnDescription = "绑定对象Code属性值", Length = 64)] + [Required] + public virtual string BindAttrCode { get; set; } = "objectGUID"; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLogDiff.cs b/Admin.NET/Admin.NET.Core/Entity/SysLogDiff.cs new file mode 100644 index 00000000..a25de06b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysLogDiff.cs @@ -0,0 +1,58 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统差异日志表 +/// +[SugarTable(null, "系统差异日志表")] +[SysTable] +[LogTable] +public partial class SysLogDiff : EntityBase +{ + /// + /// 操作前记录 + /// + [SugarColumn(ColumnDescription = "操作前记录", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? BeforeData { get; set; } + + /// + /// 操作后记录 + /// + [SugarColumn(ColumnDescription = "操作后记录", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? AfterData { get; set; } + + /// + /// Sql + /// + [SugarColumn(ColumnDescription = "Sql", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Sql { get; set; } + + /// + /// 参数 手动传入的参数 + /// + [SugarColumn(ColumnDescription = "参数", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Parameters { get; set; } + + /// + /// 业务对象 + /// + [SugarColumn(ColumnDescription = "业务对象", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? BusinessData { get; set; } + + /// + /// 差异操作 + /// + [SugarColumn(ColumnDescription = "差异操作", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? DiffType { get; set; } + + /// + /// 耗时 + /// + [SugarColumn(ColumnDescription = "耗时")] + public long? Elapsed { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLogEx.cs b/Admin.NET/Admin.NET.Core/Entity/SysLogEx.cs new file mode 100644 index 00000000..e650144c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysLogEx.cs @@ -0,0 +1,72 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统异常日志表 +/// +[SugarTable(null, "系统异常日志表")] +[SysTable] +[LogTable] +public partial class SysLogEx : SysLogVis +{ + /// + /// 请求方式 + /// + [SugarColumn(ColumnDescription = "请求方式", Length = 32)] + [MaxLength(32)] + public string? HttpMethod { get; set; } + + /// + /// 请求地址 + /// + [SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? RequestUrl { get; set; } + + /// + /// 请求参数 + /// + [SugarColumn(ColumnDescription = "请求参数", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? RequestParam { get; set; } + + /// + /// 返回结果 + /// + [SugarColumn(ColumnDescription = "返回结果", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? ReturnResult { get; set; } + + /// + /// 事件Id + /// + [SugarColumn(ColumnDescription = "事件Id")] + public int? EventId { get; set; } + + /// + /// 线程Id + /// + [SugarColumn(ColumnDescription = "线程Id")] + public int? ThreadId { get; set; } + + /// + /// 请求跟踪Id + /// + [SugarColumn(ColumnDescription = "请求跟踪Id", Length = 128)] + [MaxLength(128)] + public string? TraceId { get; set; } + + /// + /// 异常信息 + /// + [SugarColumn(ColumnDescription = "异常信息", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Exception { get; set; } + + /// + /// 日志消息Json + /// + [SugarColumn(ColumnDescription = "日志消息Json", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Message { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLogOp.cs b/Admin.NET/Admin.NET.Core/Entity/SysLogOp.cs new file mode 100644 index 00000000..e5ba1c1d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysLogOp.cs @@ -0,0 +1,72 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统操作日志表 +/// +[SugarTable(null, "系统操作日志表")] +[SysTable] +[LogTable] +public partial class SysLogOp : SysLogVis +{ + /// + /// 请求方式 + /// + [SugarColumn(ColumnDescription = "请求方式", Length = 32)] + [MaxLength(32)] + public string? HttpMethod { get; set; } + + /// + /// 请求地址 + /// + [SugarColumn(ColumnDescription = "请求地址", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? RequestUrl { get; set; } + + /// + /// 请求参数 + /// + [SugarColumn(ColumnDescription = "请求参数", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? RequestParam { get; set; } + + /// + /// 返回结果 + /// + [SugarColumn(ColumnDescription = "返回结果", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? ReturnResult { get; set; } + + /// + /// 事件Id + /// + [SugarColumn(ColumnDescription = "事件Id")] + public int? EventId { get; set; } + + /// + /// 线程Id + /// + [SugarColumn(ColumnDescription = "线程Id")] + public int? ThreadId { get; set; } + + /// + /// 请求跟踪Id + /// + [SugarColumn(ColumnDescription = "请求跟踪Id", Length = 128)] + [MaxLength(128)] + public string? TraceId { get; set; } + + /// + /// 异常信息 + /// + [SugarColumn(ColumnDescription = "异常信息", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Exception { get; set; } + + /// + /// 日志消息Json + /// + [SugarColumn(ColumnDescription = "日志消息Json", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? Message { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysLogVis.cs b/Admin.NET/Admin.NET.Core/Entity/SysLogVis.cs new file mode 100644 index 00000000..2e0928f6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysLogVis.cs @@ -0,0 +1,116 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统访问日志表 +/// +[SugarTable(null, "系统访问日志表")] +[SysTable] +[LogTable] +public partial class SysLogVis : EntityTenant +{ + /// + /// 模块名称 + /// + [SugarColumn(ColumnDescription = "模块名称", Length = 256)] + [MaxLength(256)] + public string? ControllerName { get; set; } + + /// + /// 方法名称 + /// + [SugarColumn(ColumnDescription = "方法名称", Length = 256)] + [MaxLength(256)] + public string? ActionName { get; set; } + + /// + /// 显示名称 + /// + [SugarColumn(ColumnDescription = "显示名称", Length = 256)] + [MaxLength(256)] + public string? DisplayTitle { get; set; } + + /// + /// 执行状态 + /// + [SugarColumn(ColumnDescription = "执行状态", Length = 32)] + [MaxLength(32)] + public string? Status { get; set; } + + /// + /// IP地址 + /// + [SugarColumn(ColumnDescription = "IP地址", Length = 256)] + [MaxLength(256)] + public string? RemoteIp { get; set; } + + /// + /// 登录地点 + /// + [SugarColumn(ColumnDescription = "登录地点", Length = 128)] + [MaxLength(128)] + public string? Location { get; set; } + + /// + /// 经度 + /// + [SugarColumn(ColumnDescription = "经度")] + public double? Longitude { get; set; } + + /// + /// 维度 + /// + [SugarColumn(ColumnDescription = "维度")] + public double? Latitude { get; set; } + + /// + /// 浏览器 + /// + [SugarColumn(ColumnDescription = "浏览器", Length = 1024)] + [MaxLength(1024)] + public string? Browser { get; set; } + + /// + /// 操作系统 + /// + [SugarColumn(ColumnDescription = "操作系统", Length = 256)] + [MaxLength(256)] + public string? Os { get; set; } + + /// + /// 操作用时 + /// + [SugarColumn(ColumnDescription = "操作用时")] + public long? Elapsed { get; set; } + + /// + /// 日志时间 + /// + [SugarColumn(ColumnDescription = "日志时间")] + public DateTime? LogDateTime { get; set; } + + /// + /// 日志级别 + /// + [SugarColumn(ColumnDescription = "日志级别")] + public LogLevel? LogLevel { get; set; } + + /// + /// 账号 + /// + [SugarColumn(ColumnDescription = "账号", Length = 32)] + [MaxLength(32)] + public string? Account { get; set; } + + /// + /// 真实姓名 + /// + [SugarColumn(ColumnDescription = "真实姓名", Length = 32)] + [MaxLength(32)] + public string? RealName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysMenu.cs b/Admin.NET/Admin.NET.Core/Entity/SysMenu.cs new file mode 100644 index 00000000..3f6a51a9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysMenu.cs @@ -0,0 +1,134 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统菜单表 +/// +[SugarTable(null, "系统菜单表")] +[SysTable] +[SugarIndex("index_{table}_T", nameof(Title), OrderByType.Asc)] +[SugarIndex("index_{table}_T2", nameof(Type), OrderByType.Asc)] +public partial class SysMenu : EntityBase +{ + /// + /// 父Id + /// + [SugarColumn(ColumnDescription = "父Id")] + public long Pid { get; set; } + + /// + /// 菜单类型(1目录 2菜单 3按钮) + /// + [SugarColumn(ColumnDescription = "菜单类型")] + public MenuTypeEnum Type { get; set; } + + /// + /// 路由名称 + /// + [SugarColumn(ColumnDescription = "路由名称", Length = 64)] + [MaxLength(64)] + public string? Name { get; set; } + + /// + /// 路由地址 + /// + [SugarColumn(ColumnDescription = "路由地址", Length = 128)] + [MaxLength(128)] + public string? Path { get; set; } + + /// + /// 组件路径 + /// + [SugarColumn(ColumnDescription = "组件路径", Length = 128)] + [MaxLength(128)] + public string? Component { get; set; } + + /// + /// 重定向 + /// + [SugarColumn(ColumnDescription = "重定向", Length = 128)] + [MaxLength(128)] + public string? Redirect { get; set; } + + /// + /// 权限标识 + /// + [SugarColumn(ColumnDescription = "权限标识", Length = 128)] + [MaxLength(128)] + public string? Permission { get; set; } + + /// + /// 菜单名称 + /// + [SugarColumn(ColumnDescription = "菜单名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Title { get; set; } + + /// + /// 图标 + /// + [SugarColumn(ColumnDescription = "图标", Length = 128)] + [MaxLength(128)] + public string? Icon { get; set; } + + /// + /// 是否内嵌 + /// + [SugarColumn(ColumnDescription = "是否内嵌")] + public bool IsIframe { get; set; } + + /// + /// 外链链接 + /// + [SugarColumn(ColumnDescription = "外链链接", Length = 256)] + [MaxLength(256)] + public string? OutLink { get; set; } + + /// + /// 是否隐藏 + /// + [SugarColumn(ColumnDescription = "是否隐藏")] + public bool IsHide { get; set; } + + /// + /// 是否缓存 + /// + [SugarColumn(ColumnDescription = "是否缓存")] + public bool IsKeepAlive { get; set; } = true; + + /// + /// 是否固定 + /// + [SugarColumn(ColumnDescription = "是否固定")] + public bool IsAffix { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 256)] + [MaxLength(256)] + public string? Remark { get; set; } + + /// + /// 菜单子项 + /// + [SugarColumn(IsIgnore = true)] + public List Children { get; set; } = new List(); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysNotice.cs b/Admin.NET/Admin.NET.Core/Entity/SysNotice.cs new file mode 100644 index 00000000..7aa7d012 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysNotice.cs @@ -0,0 +1,82 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统通知公告表 +/// +[SugarTable(null, "系统通知公告表")] +[SysTable] +[SugarIndex("index_{table}_T", nameof(Type), OrderByType.Asc)] +public partial class SysNotice : EntityBase +{ + /// + /// 标题 + /// + [SugarColumn(ColumnDescription = "标题", Length = 32)] + [Required, MaxLength(32)] + [SensitiveDetection('*')] + public virtual string Title { get; set; } + + /// + /// 内容 + /// + [SugarColumn(ColumnDescription = "内容", ColumnDataType = StaticConfig.CodeFirst_BigString)] + [Required] + [SensitiveDetection('*')] + public virtual string Content { get; set; } + + /// + /// 类型(1通知 2公告) + /// + [SugarColumn(ColumnDescription = "类型(1通知 2公告)")] + public NoticeTypeEnum Type { get; set; } + + /// + /// 发布人Id + /// + [SugarColumn(ColumnDescription = "发布人Id")] + public long PublicUserId { get; set; } + + /// + /// 发布人姓名 + /// + [SugarColumn(ColumnDescription = "发布人姓名", Length = 32)] + [MaxLength(32)] + public string? PublicUserName { get; set; } + + /// + /// 发布机构Id + /// + [SugarColumn(ColumnDescription = "发布机构Id")] + public long PublicOrgId { get; set; } + + /// + /// 发布机构名称 + /// + [SugarColumn(ColumnDescription = "发布机构名称", Length = 64)] + [MaxLength(64)] + public string? PublicOrgName { get; set; } + + /// + /// 发布时间 + /// + [SugarColumn(ColumnDescription = "发布时间")] + public DateTime? PublicTime { get; set; } + + /// + /// 撤回时间 + /// + [SugarColumn(ColumnDescription = "撤回时间")] + public DateTime? CancelTime { get; set; } + + /// + /// 状态(0草稿 1发布 2撤回 3删除) + /// + [SugarColumn(ColumnDescription = "状态(0草稿 1发布 2撤回 3删除)")] + public NoticeStatusEnum Status { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysNoticeUser.cs b/Admin.NET/Admin.NET.Core/Entity/SysNoticeUser.cs new file mode 100644 index 00000000..5f71ba4a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysNoticeUser.cs @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统通知公告用户表 +/// +[SugarTable(null, "系统通知公告用户表")] +[SysTable] +public partial class SysNoticeUser : EntityBaseId +{ + /// + /// 通知公告Id + /// + [SugarColumn(ColumnDescription = "通知公告Id")] + public long NoticeId { get; set; } + + /// + /// 通知公告 + /// + [Navigate(NavigateType.OneToOne, nameof(NoticeId))] + public SysNotice SysNotice { get; set; } + + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 阅读时间 + /// + [SugarColumn(ColumnDescription = "阅读时间")] + public DateTime? ReadTime { get; set; } + + /// + /// 状态(0未读 1已读) + /// + [SugarColumn(ColumnDescription = "状态(0未读 1已读)")] + public NoticeUserStatusEnum ReadStatus { get; set; } = NoticeUserStatusEnum.UNREAD; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysOAuthUser.cs b/Admin.NET/Admin.NET.Core/Entity/SysOAuthUser.cs new file mode 100644 index 00000000..b001414a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysOAuthUser.cs @@ -0,0 +1,166 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统三方用户表 +/// +[SugarTable(null, "系统三方用户表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(NickName), OrderByType.Asc)] +[SugarIndex("index_{table}_M", nameof(Mobile), OrderByType.Asc)] +public partial class SysOAuthUser : EntityBase +{ + /// + /// 系统用户Id + /// + [SugarColumn(ColumnDescription = "系统用户Id")] + public long UserId { get; set; } + + /// + /// 系统用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(UserId))] + public SysUser SysUser { get; set; } + + /// + /// 平台类型 + /// + [SugarColumn(ColumnDescription = "平台类型")] + public PlatformTypeEnum PlatformType { get; set; } = PlatformTypeEnum.微信公众号; + + /// + /// OpenId + /// + [SugarColumn(ColumnDescription = "OpenId", Length = 64)] + [MaxLength(64)] + public virtual string? OpenId { get; set; } + + /// + /// 会话密钥 + /// + [SugarColumn(ColumnDescription = "会话密钥", Length = 256)] + [MaxLength(256)] + public string? SessionKey { get; set; } + + /// + /// UnionId + /// + [SugarColumn(ColumnDescription = "UnionId", Length = 64)] + [MaxLength(64)] + public string? UnionId { get; set; } + + /// + /// 账号 + /// + [SugarColumn(ColumnDescription = "账号", Length = 128)] + [MaxLength(128)] + public string? Account { get; set; } + + /// + /// 昵称 + /// + [SugarColumn(ColumnDescription = "昵称", Length = 64)] + [Required, MaxLength(64)] + public string NickName { get; set; } + + /// + /// 头像 + /// + [SugarColumn(ColumnDescription = "头像", Length = 256)] + [MaxLength(256)] + public string? Avatar { get; set; } + + /// + /// 邮箱 + /// + [SugarColumn(ColumnDescription = "邮箱", Length = 128)] + [MaxLength(128)] + public virtual string? Email { get; set; } + + /// + /// 手机号码 + /// + [SugarColumn(ColumnDescription = "手机号码", Length = 16)] + [MaxLength(16)] + public string? Mobile { get; set; } + + /// + /// 性别 + /// + [SugarColumn(ColumnDescription = "性别")] + public int? Sex { get; set; } = 1; + + /// + /// IP 地址 + /// + [SugarColumn(ColumnDescription = "IP 地址", Length = 256)] + [MaxLength(256)] + public string? IpAddress { get; set; } + + /// + /// 语言 + /// + [SugarColumn(ColumnDescription = "语言", Length = 64)] + [MaxLength(64)] + public string? Language { get; set; } + + /// + /// 城市 + /// + [SugarColumn(ColumnDescription = "城市", Length = 64)] + [MaxLength(64)] + public string? City { get; set; } + + /// + /// 省 + /// + [SugarColumn(ColumnDescription = "省", Length = 64)] + [MaxLength(64)] + public string? Province { get; set; } + + /// + /// 国家 + /// + [SugarColumn(ColumnDescription = "国家", Length = 64)] + [MaxLength(64)] + public string? Country { get; set; } + + /// + /// AccessToken + /// + [SugarColumn(ColumnDescription = "AccessToken", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? AccessToken { get; set; } + + /// + /// RefreshToken + /// + [SugarColumn(ColumnDescription = "RefreshToken", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? RefreshToken { get; set; } + + /// + /// 过期时间 + /// + [SugarColumn(ColumnDescription = "ExpiresIn")] + public int? ExpiresIn { get; set; } + + /// + /// 用户授权的作用域,使用逗号分隔 + /// + [SugarColumn(ColumnDescription = "授权作用域", Length = 64)] + [MaxLength(64)] + public string? Scope { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysOnlineUser.cs b/Admin.NET/Admin.NET.Core/Entity/SysOnlineUser.cs new file mode 100644 index 00000000..7fd95b2d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysOnlineUser.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统在线用户表 +/// +[SugarTable(null, "系统在线用户表")] +[SysTable] +public partial class SysOnlineUser : EntityTenantId +{ + /// + /// 连接Id + /// + [SugarColumn(ColumnDescription = "连接Id")] + public string? ConnectionId { get; set; } + + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 账号 + /// + [SugarColumn(ColumnDescription = "账号", Length = 32)] + [Required, MaxLength(32)] + public virtual string UserName { get; set; } + + /// + /// 真实姓名 + /// + [SugarColumn(ColumnDescription = "真实姓名", Length = 32)] + [MaxLength(32)] + public string? RealName { get; set; } + + /// + /// 连接时间 + /// + [SugarColumn(ColumnDescription = "连接时间")] + public DateTime? Time { get; set; } + + /// + /// 连接IP + /// + [SugarColumn(ColumnDescription = "连接IP", Length = 256)] + [MaxLength(256)] + public string? Ip { get; set; } + + /// + /// 浏览器 + /// + [SugarColumn(ColumnDescription = "浏览器", Length = 128)] + [MaxLength(128)] + public string? Browser { get; set; } + + /// + /// 操作系统 + /// + [SugarColumn(ColumnDescription = "操作系统", Length = 128)] + [MaxLength(128)] + public string? Os { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs b/Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs new file mode 100644 index 00000000..68185cfb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs @@ -0,0 +1,58 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 开放接口身份表 +/// +[SugarTable(null, "开放接口身份表")] +[SysTable] +[SugarIndex("index_{table}_A", nameof(AccessKey), OrderByType.Asc)] +public partial class SysOpenAccess : EntityBase +{ + /// + /// 身份标识 + /// + [SugarColumn(ColumnDescription = "身份标识", Length = 128)] + [Required, MaxLength(128)] + public virtual string AccessKey { get; set; } + + /// + /// 密钥 + /// + [SugarColumn(ColumnDescription = "密钥", Length = 256)] + [Required, MaxLength(256)] + public virtual string AccessSecret { get; set; } + + /// + /// 绑定租户Id + /// + [SugarColumn(ColumnDescription = "绑定租户Id")] + public long BindTenantId { get; set; } + + /// + /// 绑定租户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(BindTenantId))] + public SysTenant BindTenant { get; set; } + + /// + /// 绑定用户Id + /// + [SugarColumn(ColumnDescription = "绑定用户Id")] + public virtual long BindUserId { get; set; } + + /// + /// 绑定用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(BindUserId))] + public SysUser BindUser { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysOrg.cs b/Admin.NET/Admin.NET.Core/Entity/SysOrg.cs new file mode 100644 index 00000000..f6dc77ee --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysOrg.cs @@ -0,0 +1,96 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统机构表 +/// +[SugarTable(null, "系统机构表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +[SugarIndex("index_{table}_T", nameof(Type), OrderByType.Asc)] +public partial class SysOrg : EntityTenant +{ + /// + /// 父Id + /// + [SugarColumn(ColumnDescription = "父Id")] + public long Pid { get; set; } + + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 64)] + [MaxLength(64)] + public string? Code { get; set; } + + /// + /// 级别 + /// + [SugarColumn(ColumnDescription = "级别")] + public int? Level { get; set; } + + /// + /// 机构类型-数据字典 + /// + [SugarColumn(ColumnDescription = "机构类型", Length = 64)] + [MaxLength(64)] + public string? Type { get; set; } + + /// + /// 负责人Id + /// + [SugarColumn(ColumnDescription = "负责人Id", IsNullable = true)] + public long? DirectorId { get; set; } + + /// + /// 负责人 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(DirectorId))] + public SysUser Director { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } + + /// + /// 机构子项 + /// + [SugarColumn(IsIgnore = true)] + public List Children { get; set; } + + /// + /// 是否禁止选中 + /// + [SugarColumn(IsIgnore = true)] + public bool Disabled { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysPlugin.cs b/Admin.NET/Admin.NET.Core/Entity/SysPlugin.cs new file mode 100644 index 00000000..2cc8e581 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysPlugin.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统动态插件表 +/// +[SugarTable(null, "系统动态插件表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +public partial class SysPlugin : EntityTenant +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// C#代码 + /// + [SugarColumn(ColumnDescription = "C#代码", ColumnDataType = StaticConfig.CodeFirst_BigString)] + [Required] + public virtual string CsharpCode { get; set; } + + /// + /// 程序集名称 + /// + [SugarColumn(ColumnDescription = "程序集名称", Length = 512)] + [MaxLength(512)] + public string? AssemblyName { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysPos.cs b/Admin.NET/Admin.NET.Core/Entity/SysPos.cs new file mode 100644 index 00000000..d20f060c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysPos.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统职位表 +/// +[SugarTable(null, "系统职位表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysPos : EntityTenant +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 64)] + [MaxLength(64)] + public string? Code { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysPrint.cs b/Admin.NET/Admin.NET.Core/Entity/SysPrint.cs new file mode 100644 index 00000000..d7f69c22 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysPrint.cs @@ -0,0 +1,69 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统打印模板表 +/// +[SugarTable(null, "系统打印模板表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +public partial class SysPrint : EntityTenant +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 打印模板 + /// + [SugarColumn(ColumnDescription = "打印模板", ColumnDataType = StaticConfig.CodeFirst_BigString)] + [Required] + public virtual string Template { get; set; } + + /// + /// 打印类型 + /// + [SugarColumn(ColumnDescription = "打印类型")] + [Required] + public virtual PrintTypeEnum? PrintType { get; set; } + + /// + /// 客户端服务地址 + /// + [SugarColumn(ColumnDescription = "客户端服务地址", Length = 128)] + [MaxLength(128)] + public virtual string? ClientServiceAddress { get; set; } + + /// + /// 打印参数 + /// + [SugarColumn(ColumnDescription = "打印参数", ColumnDataType = "text")] + public virtual string? PrintParam { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs b/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs new file mode 100644 index 00000000..103f20e0 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs @@ -0,0 +1,109 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统行政地区表 +/// +[SugarTable(null, "系统行政地区表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysRegion : EntityBaseId +{ + /// + /// 父Id + /// + [SugarColumn(ColumnDescription = "父Id")] + public long Pid { get; set; } + + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 128)] + [Required, MaxLength(128)] + public virtual string Name { get; set; } + + /// + /// 简称 + /// + [SugarColumn(ColumnDescription = "简称", Length = 32)] + [MaxLength(32)] + public string? ShortName { get; set; } + + /// + /// 组合名 + /// + [SugarColumn(ColumnDescription = "组合名", Length = 64)] + [MaxLength(64)] + public string? MergerName { get; set; } + + /// + /// 行政代码 + /// + [SugarColumn(ColumnDescription = "行政代码", Length = 32)] + [MaxLength(32)] + public string? Code { get; set; } + + /// + /// 邮政编码 + /// + [SugarColumn(ColumnDescription = "邮政编码", Length = 6)] + [MaxLength(6)] + public string? ZipCode { get; set; } + + /// + /// 区号 + /// + [SugarColumn(ColumnDescription = "区号", Length = 6)] + [MaxLength(6)] + public string? CityCode { get; set; } + + /// + /// 层级 + /// + [SugarColumn(ColumnDescription = "层级")] + public int Level { get; set; } + + /// + /// 拼音 + /// + [SugarColumn(ColumnDescription = "拼音", Length = 128)] + [MaxLength(128)] + public string? PinYin { get; set; } + + /// + /// 经度 + /// + [SugarColumn(ColumnDescription = "经度")] + public float Lng { get; set; } + + /// + /// 维度 + /// + [SugarColumn(ColumnDescription = "维度")] + public float Lat { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } + + /// + /// 机构子项 + /// + [SugarColumn(IsIgnore = true)] + public List Children { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRole.cs b/Admin.NET/Admin.NET.Core/Entity/SysRole.cs new file mode 100644 index 00000000..3b1de1e9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysRole.cs @@ -0,0 +1,62 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色表 +/// +[SugarTable(null, "系统角色表")] +[SysTable] +[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)] +[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)] +public partial class SysRole : EntityTenant +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + [Required, MaxLength(64)] + public virtual string Name { get; set; } + + /// + /// 编码 + /// + [SugarColumn(ColumnDescription = "编码", Length = 64)] + [MaxLength(64)] + public string? Code { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 数据范围(1全部数据 2本部门及以下数据 3本部门数据 4仅本人数据 5自定义数据) + /// + [SugarColumn(ColumnDescription = "数据范围")] + public DataScopeEnum DataScope { get; set; } = DataScopeEnum.Self; + + /// + /// 是否是内置(Y-是,N-否) + /// + [SugarColumn(ColumnDescription = "是否是内置")] + public YesNoEnum SysFlag { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRoleApi.cs b/Admin.NET/Admin.NET.Core/Entity/SysRoleApi.cs new file mode 100644 index 00000000..d6c8ad22 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysRoleApi.cs @@ -0,0 +1,28 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色接口表 +/// +[SugarTable(null, "系统角色接口表")] +[SysTable] +public class SysRoleApi : EntityBaseId +{ + /// + /// 角色Id + /// + [SugarColumn(ColumnDescription = "角色Id")] + public long RoleId { get; set; } + + /// + /// 接口路由 + /// + [SugarColumn(ColumnDescription = "接口路由", ColumnDataType = StaticConfig.CodeFirst_BigString)] + [Required] + public string Route { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRoleMenu.cs b/Admin.NET/Admin.NET.Core/Entity/SysRoleMenu.cs new file mode 100644 index 00000000..04e3f1d4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysRoleMenu.cs @@ -0,0 +1,35 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色菜单表 +/// +[SugarTable(null, "系统角色菜单表")] +[SysTable] +public class SysRoleMenu : EntityBaseId +{ + /// + /// 角色Id + /// + [SugarColumn(ColumnDescription = "角色Id")] + public long RoleId { get; set; } + + /// + /// 菜单Id + /// + [SugarColumn(ColumnDescription = "菜单Id")] + public long MenuId { get; set; } + + /// + /// 菜单 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(MenuId))] + public SysMenu SysMenu { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRoleOrg.cs b/Admin.NET/Admin.NET.Core/Entity/SysRoleOrg.cs new file mode 100644 index 00000000..c4622f83 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysRoleOrg.cs @@ -0,0 +1,35 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色机构表 +/// +[SugarTable(null, "系统角色机构表")] +[SysTable] +public class SysRoleOrg : EntityBaseId +{ + /// + /// 角色Id + /// + [SugarColumn(ColumnDescription = "角色Id")] + public long RoleId { get; set; } + + /// + /// 机构Id + /// + [SugarColumn(ColumnDescription = "机构Id")] + public long OrgId { get; set; } + + /// + /// 机构 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(OrgId))] + public SysOrg SysOrg { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysTenant.cs b/Admin.NET/Admin.NET.Core/Entity/SysTenant.cs new file mode 100644 index 00000000..9d1fa6f1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysTenant.cs @@ -0,0 +1,85 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统租户表 +/// +[SugarTable(null, "系统租户表")] +[SysTable] +public partial class SysTenant : EntityBase +{ + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 机构Id + /// + [SugarColumn(ColumnDescription = "机构Id")] + public long OrgId { get; set; } + + /// + /// 主机 + /// + [SugarColumn(ColumnDescription = "主机", Length = 128)] + [MaxLength(128)] + public string? Host { get; set; } + + /// + /// 租户类型 + /// + [SugarColumn(ColumnDescription = "租户类型")] + public TenantTypeEnum TenantType { get; set; } + + /// + /// 数据库类型 + /// + [SugarColumn(ColumnDescription = "数据库类型")] + public SqlSugar.DbType DbType { get; set; } + + /// + /// 数据库连接 + /// + [SugarColumn(ColumnDescription = "数据库连接", Length = 256)] + [MaxLength(256)] + public string? Connection { get; set; } + + /// + /// 数据库标识 + /// + [SugarColumn(ColumnDescription = "数据库标识", Length = 64)] + [MaxLength(64)] + public string? ConfigId { get; set; } + + /// + /// 从库连接/读写分离 + /// + [SugarColumn(ColumnDescription = "从库连接/读写分离", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? SlaveConnections { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 128)] + [MaxLength(128)] + public string? Remark { get; set; } + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUser.cs b/Admin.NET/Admin.NET.Core/Entity/SysUser.cs new file mode 100644 index 00000000..eea9f1df --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysUser.cs @@ -0,0 +1,315 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户表 +/// +[SugarTable(null, "系统用户表")] +[SysTable] +[SugarIndex("index_{table}_A", nameof(Account), OrderByType.Asc)] +[SugarIndex("index_{table}_P", nameof(Phone), OrderByType.Asc)] +public partial class SysUser : EntityTenant +{ + /// + /// 账号 + /// + [SugarColumn(ColumnDescription = "账号", Length = 32)] + [Required, MaxLength(32)] + public virtual string Account { get; set; } + + /// + /// 密码 + /// + [SugarColumn(ColumnDescription = "密码", Length = 512)] + [MaxLength(512)] + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public virtual string Password { get; set; } + + /// + /// 真实姓名 + /// + [SugarColumn(ColumnDescription = "真实姓名", Length = 32)] + [MaxLength(32)] + public virtual string RealName { get; set; } + + /// + /// 昵称 + /// + [SugarColumn(ColumnDescription = "昵称", Length = 32)] + [MaxLength(32)] + public string? NickName { get; set; } + + /// + /// 头像 + /// + [SugarColumn(ColumnDescription = "头像", Length = 512)] + [MaxLength(512)] + public string? Avatar { get; set; } + + /// + /// 性别-男_1、女_2 + /// + [SugarColumn(ColumnDescription = "性别")] + public GenderEnum Sex { get; set; } = GenderEnum.Male; + + /// + /// 年龄 + /// + [SugarColumn(ColumnDescription = "年龄")] + public int Age { get; set; } + + /// + /// 出生日期 + /// + [SugarColumn(ColumnDescription = "出生日期")] + public DateTime? Birthday { get; set; } + + /// + /// 民族 + /// + [SugarColumn(ColumnDescription = "民族", Length = 32)] + [MaxLength(32)] + public string? Nation { get; set; } + + /// + /// 手机号码 + /// + [SugarColumn(ColumnDescription = "手机号码", Length = 16)] + [MaxLength(16)] + public string? Phone { get; set; } + + /// + /// 证件类型 + /// + [SugarColumn(ColumnDescription = "证件类型")] + public CardTypeEnum CardType { get; set; } + + /// + /// 身份证号 + /// + [SugarColumn(ColumnDescription = "身份证号", Length = 32)] + [MaxLength(32)] + public string? IdCardNum { get; set; } + + /// + /// 邮箱 + /// + [SugarColumn(ColumnDescription = "邮箱", Length = 64)] + [MaxLength(64)] + public string? Email { get; set; } + + /// + /// 地址 + /// + [SugarColumn(ColumnDescription = "地址", Length = 256)] + [MaxLength(256)] + public string? Address { get; set; } + + /// + /// 文化程度 + /// + [SugarColumn(ColumnDescription = "文化程度")] + public CultureLevelEnum CultureLevel { get; set; } + + /// + /// 政治面貌 + /// + [SugarColumn(ColumnDescription = "政治面貌", Length = 16)] + [MaxLength(16)] + public string? PoliticalOutlook { get; set; } + + /// + /// 毕业院校 + /// COLLEGE + [SugarColumn(ColumnDescription = "毕业院校", Length = 128)] + [MaxLength(128)] + public string? College { get; set; } + + /// + /// 办公电话 + /// + [SugarColumn(ColumnDescription = "办公电话", Length = 16)] + [MaxLength(16)] + public string? OfficePhone { get; set; } + + /// + /// 紧急联系人 + /// + [SugarColumn(ColumnDescription = "紧急联系人", Length = 32)] + [MaxLength(32)] + public string? EmergencyContact { get; set; } + + /// + /// 紧急联系人电话 + /// + [SugarColumn(ColumnDescription = "紧急联系人电话", Length = 16)] + [MaxLength(16)] + public string? EmergencyPhone { get; set; } + + /// + /// 紧急联系人地址 + /// + [SugarColumn(ColumnDescription = "紧急联系人地址", Length = 256)] + [MaxLength(256)] + public string? EmergencyAddress { get; set; } + + /// + /// 个人简介 + /// + [SugarColumn(ColumnDescription = "个人简介", Length = 512)] + [MaxLength(512)] + public string? Introduction { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnDescription = "排序")] + public int OrderNo { get; set; } = 100; + + /// + /// 状态 + /// + [SugarColumn(ColumnDescription = "状态")] + public StatusEnum Status { get; set; } = StatusEnum.Enable; + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 256)] + [MaxLength(256)] + public string? Remark { get; set; } + + /// + /// 账号类型 + /// + [SugarColumn(ColumnDescription = "账号类型")] + public AccountTypeEnum AccountType { get; set; } = AccountTypeEnum.NormalUser; + + /// + /// 直属机构Id + /// + [SugarColumn(ColumnDescription = "直属机构Id")] + public long OrgId { get; set; } + + /// + /// 直属机构 + /// + [Navigate(NavigateType.OneToOne, nameof(OrgId))] + public SysOrg SysOrg { get; set; } + + /// + /// 直属主管Id + /// + [SugarColumn(ColumnDescription = "直属主管Id")] + public long? ManagerUserId { get; set; } + + /// + /// 直属主管 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(ManagerUserId))] + public SysUser ManagerUser { get; set; } + + /// + /// 职位Id + /// + [SugarColumn(ColumnDescription = "职位Id")] + public long PosId { get; set; } + + /// + /// 职位 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(PosId))] + public SysPos SysPos { get; set; } + + /// + /// 工号 + /// + [SugarColumn(ColumnDescription = "工号", Length = 32)] + [MaxLength(32)] + public string? JobNum { get; set; } + + /// + /// 职级 + /// + [SugarColumn(ColumnDescription = "职级", Length = 32)] + [MaxLength(32)] + public string? PosLevel { get; set; } + + /// + /// 职称 + /// + [SugarColumn(ColumnDescription = "职称", Length = 32)] + [MaxLength(32)] + public string? PosTitle { get; set; } + + /// + /// 擅长领域 + /// + [SugarColumn(ColumnDescription = "擅长领域", Length = 32)] + [MaxLength(32)] + public string? Expertise { get; set; } + + /// + /// 办公区域 + /// + [SugarColumn(ColumnDescription = "办公区域", Length = 32)] + [MaxLength(32)] + public string? OfficeZone { get; set; } + + /// + /// 办公室 + /// + [SugarColumn(ColumnDescription = "办公室", Length = 32)] + [MaxLength(32)] + public string? Office { get; set; } + + /// + /// 入职日期 + /// + [SugarColumn(ColumnDescription = "入职日期")] + public DateTime? JoinDate { get; set; } + + /// + /// 最新登录Ip + /// + [SugarColumn(ColumnDescription = "最新登录Ip", Length = 256)] + [MaxLength(256)] + public string? LastLoginIp { get; set; } + + /// + /// 最新登录地点 + /// + [SugarColumn(ColumnDescription = "最新登录地点", Length = 128)] + [MaxLength(128)] + public string? LastLoginAddress { get; set; } + + /// + /// 最新登录时间 + /// + [SugarColumn(ColumnDescription = "最新登录时间")] + public DateTime? LastLoginTime { get; set; } + + /// + /// 最新登录设备 + /// + [SugarColumn(ColumnDescription = "最新登录设备", Length = 128)] + [MaxLength(128)] + public string? LastLoginDevice { get; set; } + + /// + /// 电子签名 + /// + [SugarColumn(ColumnDescription = "电子签名", Length = 512)] + [MaxLength(512)] + public string? Signature { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUserExtOrg.cs b/Admin.NET/Admin.NET.Core/Entity/SysUserExtOrg.cs new file mode 100644 index 00000000..cc8c3cb7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysUserExtOrg.cs @@ -0,0 +1,77 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户扩展机构表 +/// +[SugarTable(null, "系统用户扩展机构表")] +[SysTable] +public partial class SysUserExtOrg : EntityBaseId +{ + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(UserId))] + public SysUser SysUser { get; set; } + + /// + /// 机构Id + /// + [SugarColumn(ColumnDescription = "机构Id")] + public long OrgId { get; set; } + + /// + /// 机构 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(OrgId))] + public SysOrg SysOrg { get; set; } + + /// + /// 职位Id + /// + [SugarColumn(ColumnDescription = "职位Id")] + public long PosId { get; set; } + + /// + /// 职位 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(PosId))] + public SysPos SysPos { get; set; } + + /// + /// 工号 + /// + [SugarColumn(ColumnDescription = "工号", Length = 32)] + [MaxLength(32)] + public string? JobNum { get; set; } + + /// + /// 职级 + /// + [SugarColumn(ColumnDescription = "职级", Length = 32)] + [MaxLength(32)] + public string? PosLevel { get; set; } + + /// + /// 入职日期 + /// + [SugarColumn(ColumnDescription = "入职日期")] + public DateTime? JoinDate { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUserLdap.cs b/Admin.NET/Admin.NET.Core/Entity/SysUserLdap.cs new file mode 100644 index 00000000..f2e0b74e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysUserLdap.cs @@ -0,0 +1,44 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户域配置表 +/// +[SugarTable(null, "系统用户域配置表")] +[SysTable] +[SugarIndex("index_{table}_A", nameof(Account), OrderByType.Asc)] +[SugarIndex("index_{table}_U", nameof(UserId), OrderByType.Asc)] +public class SysUserLdap : EntityTenant +{ + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 域账号 + /// AD域对应sAMAccountName + /// Ldap对应uid + /// + [SugarColumn(ColumnDescription = "域账号", Length = 32)] + [Required] + public string Account { get; set; } + + /// + /// 对应EmployeeId(用于数据导入对照) + /// + [SugarColumn(ColumnDescription = "对应EmployeeId", Length = 32)] + public string? EmployeeId { get; set; } + + /// + /// 组织代码 + /// + [SugarColumn(ColumnDescription = "组织代码", Length = 64)] + public string? DeptCode { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUserMenu.cs b/Admin.NET/Admin.NET.Core/Entity/SysUserMenu.cs new file mode 100644 index 00000000..ee2a6833 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysUserMenu.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户菜单快捷导航表 +/// +[SugarTable(null, "系统用户菜单快捷导航表")] +[SysTable] +public partial class SysUserMenu : EntityBaseId +{ + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(UserId))] + public SysUser SysUser { get; set; } + + /// + /// 菜单Id + /// + [SugarColumn(ColumnDescription = "菜单Id")] + public long MenuId { get; set; } + + /// + /// 菜单 + /// + [Navigate(NavigateType.OneToOne, nameof(MenuId))] + public SysMenu SysMenu { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUserRole.cs b/Admin.NET/Admin.NET.Core/Entity/SysUserRole.cs new file mode 100644 index 00000000..8d5cff1f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysUserRole.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户角色表 +/// +[SugarTable(null, "系统用户角色表")] +[SysTable] +public class SysUserRole : EntityBaseId +{ + /// + /// 用户Id + /// + [SugarColumn(ColumnDescription = "用户Id")] + public long UserId { get; set; } + + /// + /// 用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(UserId))] + public SysUser SysUser { get; set; } + + /// + /// 角色Id + /// + [SugarColumn(ColumnDescription = "角色Id")] + public long RoleId { get; set; } + + /// + /// 角色 + /// + [Navigate(NavigateType.OneToOne, nameof(RoleId))] + public SysRole SysRole { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs new file mode 100644 index 00000000..62e5c127 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs @@ -0,0 +1,165 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统微信支付表 +/// +[SugarTable(null, "系统微信支付表")] +[SysTable] +public partial class SysWechatPay : EntityBase +{ + /// + /// 微信商户号 + /// + [SugarColumn(ColumnDescription = "微信商户号")] + [Required] + public virtual string MerchantId { get; set; } + + /// + /// 服务商AppId + /// + [SugarColumn(ColumnDescription = "服务商AppId")] + [Required] + public virtual string AppId { get; set; } + + /// + /// 商户订单号 + /// + [SugarColumn(ColumnDescription = "商户订单号")] + [Required] + public virtual string OutTradeNumber { get; set; } + + /// + /// 支付订单号 + /// + [SugarColumn(ColumnDescription = "支付订单号")] + [Required] + public virtual string TransactionId { get; set; } + + /// + /// 交易类型 + /// + [SugarColumn(ColumnDescription = "交易类型")] + public string? TradeType { get; set; } + + /// + /// 交易状态 + /// + [SugarColumn(ColumnDescription = "交易状态")] + public string? TradeState { get; set; } + + /// + /// 交易状态描述 + /// + [SugarColumn(ColumnDescription = "交易状态描述")] + public string? TradeStateDescription { get; set; } + + /// + /// 付款银行类型 + /// + [SugarColumn(ColumnDescription = "付款银行类型")] + public string? BankType { get; set; } + + /// + /// 订单总金额 + /// + [SugarColumn(ColumnDescription = "订单总金额")] + public int Total { get; set; } + + /// + /// 用户支付金额 + /// + [SugarColumn(ColumnDescription = "用户支付金额")] + public int? PayerTotal { get; set; } + + /// + /// 支付完成时间 + /// + [SugarColumn(ColumnDescription = "支付完成时间")] + public DateTimeOffset? SuccessTime { get; set; } + + /// + /// 交易结束时间 + /// + [SugarColumn(ColumnDescription = "交易结束时间")] + public DateTimeOffset? ExpireTime { get; set; } + + /// + /// 商品描述 + /// + [SugarColumn(ColumnDescription = "商品描述")] + public string? Description { get; set; } + + /// + /// 场景信息 + /// + [SugarColumn(ColumnDescription = "场景信息")] + public string? Scene { get; set; } + + /// + /// 附加数据 + /// + [SugarColumn(ColumnDescription = "附加数据")] + public string? Attachment { get; set; } + + /// + /// 优惠标记 + /// + [SugarColumn(ColumnDescription = "优惠标记")] + public string? GoodsTag { get; set; } + + /// + /// 结算信息 + /// + [SugarColumn(ColumnDescription = "结算信息")] + public string? Settlement { get; set; } + + /// + /// 回调通知地址 + /// + [SugarColumn(ColumnDescription = "回调通知地址")] + public string? NotifyUrl { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注")] + public string? Remark { get; set; } + + /// + /// 微信OpenId标识 + /// + [SugarColumn(ColumnDescription = "微信OpenId标识")] + public string? OpenId { get; set; } + + /// + /// 关联微信用户 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Navigate(NavigateType.OneToOne, nameof(OpenId))] + public SysOAuthUser SysOAuthUser { get; set; } + + /// + /// 子商户号 + /// + [SugarColumn(ColumnDescription = "子商户号")] + public string? SubMerchantId { get; set; } + + /// + /// 子商户AppId + /// + [SugarColumn(ColumnDescription = "回调通知地址")] + public string? SubAppId { get; set; } + + /// + /// 子商户唯一标识 + /// + [SugarColumn(ColumnDescription = "子商户唯一标识")] + public string? SubOpenId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/AccountTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/AccountTypeEnum.cs new file mode 100644 index 00000000..bedbec66 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/AccountTypeEnum.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 账号类型枚举 +/// +[Description("账号类型枚举")] +public enum AccountTypeEnum +{ + /// + /// 超级管理员 + /// + [Description("超级管理员")] + SuperAdmin = 999, + + /// + /// 系统管理员 + /// + [Description("系统管理员")] + SysAdmin = 888, + + /// + /// 普通账号 + /// + [Description("普通账号")] + NormalUser = 777, + + /// + /// 会员 + /// + [Description("会员")] + Member = 666, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/CacheTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/CacheTypeEnum.cs new file mode 100644 index 00000000..e6832ed2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/CacheTypeEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 缓存类型枚举 +/// +[Description("缓存类型枚举")] +public enum CacheTypeEnum +{ + /// + /// 内存缓存 + /// + [Description("内存缓存")] + Memory, + + /// + /// Redis缓存 + /// + [Description("Redis缓存")] + Redis +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/CardTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/CardTypeEnum.cs new file mode 100644 index 00000000..774359b0 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/CardTypeEnum.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 证件类型枚举 +/// +[Description("证件类型枚举")] +public enum CardTypeEnum +{ + /// + /// 身份证 + /// + [Description("身份证")] + IdCard = 0, + + /// + /// 护照 + /// + [Description("护照")] + PassportCard = 1, + + /// + /// 出生证 + /// + [Description("出生证")] + BirthCard = 2, + + /// + /// 港澳台通行证 + /// + [Description("港澳台通行证")] + GatCard = 3, + + /// + /// 外国人居留证 + /// + [Description("外国人居留证")] + ForeignCard = 4, + + /// + /// 营业执照 + /// + [Description("营业执照")] + License = 5, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/CryptogramEnum.cs b/Admin.NET/Admin.NET.Core/Enum/CryptogramEnum.cs new file mode 100644 index 00000000..e259beaa --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/CryptogramEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 密码加密枚举 +/// +[Description("密码加密枚举")] +public enum CryptogramEnum +{ + /// + /// MD5 + /// + [Description("MD5")] + MD5 = 0, + + /// + /// SM2(国密) + /// + [Description("SM2")] + SM2 = 1, + + /// + /// SM4(国密) + /// + [Description("SM4")] + SM4 = 2 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/CultureLevelEnum.cs b/Admin.NET/Admin.NET.Core/Enum/CultureLevelEnum.cs new file mode 100644 index 00000000..56b325e3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/CultureLevelEnum.cs @@ -0,0 +1,92 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 文化程度枚举 +/// +[Description("文化程度枚举")] +public enum CultureLevelEnum +{ + /// + /// 其他 + /// + [Description("其他")] + Level0 = 0, + + /// + /// 文盲 + /// + [Description("文盲")] + Level1 = 1, + + /// + /// 小学 + /// + [Description("小学")] + Level2 = 2, + + /// + /// 初中 + /// + [Description("初中")] + Level3 = 3, + + /// + /// 普通高中 + /// + [Description("普通高中")] + Level4 = 4, + + /// + /// 技工学校 + /// + [Description("技工学校")] + Level5 = 5, + + /// + /// 职业教育 + /// + [Description("职业教育")] + Level6 = 6, + + /// + /// 职业高中 + /// + [Description("职业高中")] + Level7 = 7, + + /// + /// 中等专科 + /// + [Description("中等专科")] + Level8 = 8, + + /// + /// 大学专科 + /// + [Description("大学专科")] + Level9 = 9, + + /// + /// 大学本科 + /// + [Description("大学本科")] + Level10 = 10, + + /// + /// 硕士研究生 + /// + [Description("硕士研究生")] + Level11 = 11, + + /// + /// 博士研究生 + /// + [Description("博士研究生")] + Level12 = 12, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/DataOpTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/DataOpTypeEnum.cs new file mode 100644 index 00000000..f4c04eac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/DataOpTypeEnum.cs @@ -0,0 +1,92 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 数据操作类型枚举 +/// +[Description("数据操作类型枚举")] +public enum DataOpTypeEnum +{ + /// + /// 其它 + /// + [Description("其它")] + Other, + + /// + /// 增加 + /// + [Description("增加")] + Add, + + /// + /// 删除 + /// + [Description("删除")] + Delete, + + /// + /// 编辑 + /// + [Description("编辑")] + Edit, + + /// + /// 更新 + /// + [Description("更新")] + Update, + + /// + /// 查询 + /// + [Description("查询")] + Query, + + /// + /// 详情 + /// + [Description("详情")] + Detail, + + /// + /// 树 + /// + [Description("树")] + Tree, + + /// + /// 导入 + /// + [Description("导入")] + Import, + + /// + /// 导出 + /// + [Description("导出")] + Export, + + /// + /// 授权 + /// + [Description("授权")] + Grant, + + /// + /// 强退 + /// + [Description("强退")] + Force, + + /// + /// 清空 + /// + [Description("清空")] + Clean +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/DataScopeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/DataScopeEnum.cs new file mode 100644 index 00000000..d6fc8bde --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/DataScopeEnum.cs @@ -0,0 +1,44 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 角色数据范围枚举 +/// +[Description("角色数据范围枚举")] +public enum DataScopeEnum +{ + /// + /// 全部数据 + /// + [Description("全部数据")] + All = 1, + + /// + /// 本部门及以下数据 + /// + [Description("本部门及以下数据")] + DeptChild = 2, + + /// + /// 本部门数据 + /// + [Description("本部门数据")] + Dept = 3, + + /// + /// 仅本人数据 + /// + [Description("仅本人数据")] + Self = 4, + + /// + /// 自定义数据 + /// + [Description("自定义数据")] + Define = 5 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/ElasticSearchAuthTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/ElasticSearchAuthTypeEnum.cs new file mode 100644 index 00000000..60f290f3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/ElasticSearchAuthTypeEnum.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// ES认证类型枚举 +/// https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/_options_on_elasticsearchclientsettings.html +/// +[Description("ES认证类型枚举")] +public enum ElasticSearchAuthTypeEnum +{ + /// + /// BasicAuthentication + /// + [Description("BasicAuthentication")] + Basic = 1, + + /// + /// ApiKey + /// + [Description("ApiKey")] + ApiKey = 2, + + /// + /// Base64ApiKey + /// + [Description("Base64ApiKey")] + Base64ApiKey = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs new file mode 100644 index 00000000..104fdffb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs @@ -0,0 +1,699 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统错误码 +/// +[ErrorCodeType] +[Description("系统错误码")] +public enum ErrorCodeEnum +{ + /// + /// 验证码错误 + /// + [ErrorCodeItemMetadata("验证码错误")] + D0008, + + /// + /// 账号不存在 + /// + [ErrorCodeItemMetadata("账号不存在")] + D0009, + + /// + /// 密匙不匹配 + /// + [ErrorCodeItemMetadata("密匙不匹配")] + D0010, + + /// + /// 密码不正确 + /// + [ErrorCodeItemMetadata("密码不正确")] + D1000, + + /// + /// 非法操作!禁止删除自己 + /// + [ErrorCodeItemMetadata("非法操作,禁止删除自己")] + D1001, + + /// + /// 记录不存在 + /// + [ErrorCodeItemMetadata("记录不存在")] + D1002, + + /// + /// 账号已存在 + /// + [ErrorCodeItemMetadata("账号已存在")] + D1003, + + /// + /// 旧密码不匹配 + /// + [ErrorCodeItemMetadata("旧密码输入错误")] + D1004, + + /// + /// 测试数据禁止更改admin密码 + /// + [ErrorCodeItemMetadata("测试数据禁止更改用户【admin】密码")] + D1005, + + /// + /// 数据已存在 + /// + [ErrorCodeItemMetadata("数据已存在")] + D1006, + + /// + /// 数据不存在或含有关联引用,禁止删除 + /// + [ErrorCodeItemMetadata("数据不存在或含有关联引用,禁止删除")] + D1007, + + /// + /// 禁止为管理员分配角色 + /// + [ErrorCodeItemMetadata("禁止为管理员分配角色")] + D1008, + + /// + /// 重复数据或记录含有不存在数据 + /// + [ErrorCodeItemMetadata("重复数据或记录含有不存在数据")] + D1009, + + /// + /// 禁止为超级管理员角色分配权限 + /// + [ErrorCodeItemMetadata("禁止为超级管理员角色分配权限")] + D1010, + + /// + /// 非法操作,未登录 + /// + [ErrorCodeItemMetadata("非法操作,未登录")] + D1011, + + /// + /// Id不能为空 + /// + [ErrorCodeItemMetadata("Id不能为空")] + D1012, + + /// + /// 所属机构不在自己的数据范围内 + /// + [ErrorCodeItemMetadata("没有权限操作该数据")] + D1013, + + /// + /// 禁止删除超级管理员 + /// + [ErrorCodeItemMetadata("禁止删除超级管理员")] + D1014, + + /// + /// 禁止修改超级管理员状态 + /// + [ErrorCodeItemMetadata("禁止修改超级管理员状态")] + D1015, + + /// + /// 没有权限 + /// + [ErrorCodeItemMetadata("没有权限")] + D1016, + + /// + /// 账号已冻结 + /// + [ErrorCodeItemMetadata("账号已冻结")] + D1017, + + /// + /// 禁止删除管理员 + /// + [ErrorCodeItemMetadata("禁止删除管理员")] + D1018, + + /// + /// 禁止删除系统管理员角色 + /// + [ErrorCodeItemMetadata("禁止删除系统管理员角色")] + D1019, + + /// + /// 禁止修改系统管理员角色 + /// + [ErrorCodeItemMetadata("禁止修改系统管理员角色")] + D1020, + + /// + /// 禁止为系统管理员角色分配权限 + /// + [ErrorCodeItemMetadata("禁止为系统管理员角色分配权限")] + D1021, + + /// + /// 禁止为超级管理员分配角色 + /// + [ErrorCodeItemMetadata("禁止为超级管理员分配角色")] + D1022, + + /// + /// 禁止删除默认租户 + /// + [ErrorCodeItemMetadata("禁止删除默认租户")] + D1023, + + /// + /// 已将其他地方登录账号下线 + /// + [ErrorCodeItemMetadata("已将其他地方登录账号下线")] + D1024, + + /// + /// 此角色下面存在账号禁止删除 + /// + [ErrorCodeItemMetadata("此角色下面存在账号禁止删除")] + D1025, + + /// + /// 禁止修改本人账号状态 + /// + [ErrorCodeItemMetadata("禁止修改本人账号状态")] + D1026, + + /// + /// 密码错误次数过多,账号已锁定,请半小时后重试! + /// + [ErrorCodeItemMetadata("密码错误次数过多,账号已锁定,请半小时后重试!")] + D1027, + + /// + /// 新密码不能与旧密码相同 + /// + [ErrorCodeItemMetadata("新密码不能与旧密码相同")] + D1028, + + /// + /// 父机构不存在 + /// + [ErrorCodeItemMetadata("父机构不存在")] + D2000, + + /// + /// 当前机构Id不能与父机构Id相同 + /// + [ErrorCodeItemMetadata("当前机构Id不能与父机构Id相同")] + D2001, + + /// + /// 已有相同组织机构,编码或名称相同 + /// + [ErrorCodeItemMetadata("已有相同组织机构,编码或名称相同")] + D2002, + + /// + /// 没有权限操作机构 + /// + [ErrorCodeItemMetadata("没有权限操作机构")] + D2003, + + /// + /// 该机构下有用户禁止删除 + /// + [ErrorCodeItemMetadata("该机构下有用户禁止删除")] + D2004, + + /// + /// 附属机构下有用户禁止删除 + /// + [ErrorCodeItemMetadata("附属机构下有用户禁止删除")] + D2005, + + /// + /// 只能增加下级机构 + /// + [ErrorCodeItemMetadata("只能增加下级机构")] + D2006, + + /// + /// 下级机构下有用户禁止删除 + /// + [ErrorCodeItemMetadata("下级机构下有用户禁止删除")] + D2007, + + /// + /// 租户默认机构禁止删除 + /// + [ErrorCodeItemMetadata("租户默认机构禁止删除")] + D2008, + + /// + /// 禁止增加根节点机构 + /// + [ErrorCodeItemMetadata("禁止增加根节点机构")] + D2009, + + /// + /// 字典类型不存在 + /// + [ErrorCodeItemMetadata("字典类型不存在")] + D3000, + + /// + /// 字典类型已存在 + /// + [ErrorCodeItemMetadata("字典类型已存在,名称或编码重复")] + D3001, + + /// + /// 字典类型下面有字典值禁止删除 + /// + [ErrorCodeItemMetadata("字典类型下面有字典值禁止删除")] + D3002, + + /// + /// 字典值已存在 + /// + [ErrorCodeItemMetadata("字典值已存在,名称或编码重复")] + D3003, + + /// + /// 字典值不存在 + /// + [ErrorCodeItemMetadata("字典值不存在")] + D3004, + + /// + /// 字典状态错误 + /// + [ErrorCodeItemMetadata("字典状态错误")] + D3005, + + /// + /// 菜单已存在 + /// + [ErrorCodeItemMetadata("菜单已存在")] + D4000, + + /// + /// 路由地址为空 + /// + [ErrorCodeItemMetadata("路由地址为空")] + D4001, + + /// + /// 打开方式为空 + /// + [ErrorCodeItemMetadata("打开方式为空")] + D4002, + + /// + /// 权限标识格式为空 + /// + [ErrorCodeItemMetadata("权限标识格式为空")] + D4003, + + /// + /// 权限标识格式错误 + /// + [ErrorCodeItemMetadata("权限标识格式错误 如xxx:xxx")] + D4004, + + /// + /// 权限不存在 + /// + [ErrorCodeItemMetadata("权限不存在")] + D4005, + + /// + /// 父级菜单不能为当前节点,请重新选择父级菜单 + /// + [ErrorCodeItemMetadata("父级菜单不能为当前节点,请重新选择父级菜单")] + D4006, + + /// + /// 不能移动根节点 + /// + [ErrorCodeItemMetadata("不能移动根节点")] + D4007, + + /// + /// 禁止本节点与父节点相同 + /// + [ErrorCodeItemMetadata("禁止本节点与父节点相同")] + D4008, + + /// + /// 路由名称重复 + /// + [ErrorCodeItemMetadata("路由名称重复")] + D4009, + + /// + /// 父节点不能为按钮类型 + /// + [ErrorCodeItemMetadata("父节点不能为按钮类型")] + D4010, + + /// + /// 已存在同名或同编码应用 + /// + [ErrorCodeItemMetadata("已存在同名或同编码应用")] + D5000, + + /// + /// 默认激活系统只能有一个 + /// + [ErrorCodeItemMetadata("默认激活系统只能有一个")] + D5001, + + /// + /// 该应用下有菜单禁止删除 + /// + [ErrorCodeItemMetadata("该应用下有菜单禁止删除")] + D5002, + + /// + /// 已存在同名或同编码应用 + /// + [ErrorCodeItemMetadata("已存在同名或同编码应用")] + D5003, + + /// + /// 已存在同名或同编码职位 + /// + [ErrorCodeItemMetadata("已存在同名或同编码职位")] + D6000, + + /// + /// 该职位下有用户禁止删除 + /// + [ErrorCodeItemMetadata("该职位下有用户禁止删除")] + D6001, + + /// + /// 无权修改本职位 + /// + [ErrorCodeItemMetadata("无权修改本职位")] + D6002, + + /// + /// 职位不存在 + /// + [ErrorCodeItemMetadata("职位不存在")] + D6003, + + /// + /// 通知公告状态错误 + /// + [ErrorCodeItemMetadata("通知公告状态错误")] + D7000, + + /// + /// 通知公告删除失败 + /// + [ErrorCodeItemMetadata("通知公告删除失败")] + D7001, + + /// + /// 通知公告编辑失败 + /// + [ErrorCodeItemMetadata("通知公告编辑失败,类型必须为草稿")] + D7002, + + /// + /// 通知公告操作失败,非发布者不能进行操作 + /// + [ErrorCodeItemMetadata("通知公告操作失败,非发布者不能进行操作")] + D7003, + + /// + /// 文件不存在 + /// + [ErrorCodeItemMetadata("文件不存在")] + D8000, + + /// + /// 不允许的文件类型 + /// + [ErrorCodeItemMetadata("不允许的文件类型")] + D8001, + + /// + /// 文件超过允许大小 + /// + [ErrorCodeItemMetadata("文件超过允许大小")] + D8002, + + /// + /// 文件后缀错误 + /// + [ErrorCodeItemMetadata("文件后缀错误")] + D8003, + + /// + /// 文件已存在 + /// + [ErrorCodeItemMetadata("文件已存在")] + D8004, + + /// + /// 已存在同名或同编码参数配置 + /// + [ErrorCodeItemMetadata("已存在同名或同编码参数配置")] + D9000, + + /// + /// 禁止删除系统参数 + /// + [ErrorCodeItemMetadata("禁止删除系统参数")] + D9001, + + /// + /// 已存在同名任务调度 + /// + [ErrorCodeItemMetadata("已存在同名任务调度")] + D1100, + + /// + /// 任务调度不存在 + /// + [ErrorCodeItemMetadata("任务调度不存在")] + D1101, + + /// + /// 演示环境禁止修改数据 + /// + [ErrorCodeItemMetadata("演示环境禁止修改数据")] + D1200, + + /// + /// 已存在同名的租户 + /// + [ErrorCodeItemMetadata("已存在同名的租户")] + D1300, + + /// + /// 已存在同名的租户管理员 + /// + [ErrorCodeItemMetadata("已存在同名的租户管理员")] + D1301, + + /// + /// 租户从库配置错误 + /// + [ErrorCodeItemMetadata("租户从库配置错误")] + D1302, + + /// + /// 该表代码模板已经生成过 + /// + [ErrorCodeItemMetadata("该表代码模板已经生成过")] + D1400, + + /// + /// 该类型不存在 + /// + [ErrorCodeItemMetadata("该类型不存在")] + D1501, + + /// + /// 该字段不存在 + /// + [ErrorCodeItemMetadata("该字段不存在")] + D1502, + + /// + /// 该类型不是枚举类型 + /// + [ErrorCodeItemMetadata("该类型不是枚举类型")] + D1503, + + /// + /// 该实体不存在 + /// + [ErrorCodeItemMetadata("该实体不存在")] + D1504, + + /// + /// 父菜单不存在 + /// + [ErrorCodeItemMetadata("父菜单不存在")] + D1505, + + /// + /// 父资源不存在 + /// + [ErrorCodeItemMetadata("父资源不存在")] + D1600, + + /// + /// 当前资源Id不能与父资源Id相同 + /// + [ErrorCodeItemMetadata("当前资源Id不能与父资源Id相同")] + D1601, + + /// + /// 已有相同编码或名称 + /// + [ErrorCodeItemMetadata("已有相同编码或名称")] + D1602, + + /// + /// 脚本代码不能为空 + /// + [ErrorCodeItemMetadata("脚本代码不能为空")] + D1701, + + /// + /// 脚本代码中的作业类,需要定义 [JobDetail] 特性 + /// + [ErrorCodeItemMetadata("脚本代码中的作业类,需要定义 [JobDetail] 特性")] + D1702, + + /// + /// 作业编号需要与脚本代码中的作业类 [JobDetail('jobId')] 一致 + /// + [ErrorCodeItemMetadata("作业编号需要与脚本代码中的作业类 [JobDetail('jobId')] 一致")] + D1703, + + /// + /// 禁止修改作业编号 + /// + [ErrorCodeItemMetadata("禁止修改作业编号")] + D1704, + + /// + /// 执行作业失败 + /// + [ErrorCodeItemMetadata("执行作业失败")] + D1705, + + /// + /// 已存在同名打印模板 + /// + [ErrorCodeItemMetadata("已存在同名打印模板")] + D1800, + + /// + /// 已存在同名功能或同名程序及插件 + /// + [ErrorCodeItemMetadata("已存在同名功能或同名程序及插件")] + D1900, + + /// + /// 已存在同名或同编码项目 + /// + [ErrorCodeItemMetadata("已存在同名或同编码项目")] + xg1000, + + /// + /// 已存在相同证件号码人员 + /// + [ErrorCodeItemMetadata("已存在相同证件号码人员")] + xg1001, + + /// + /// 检测数据不存在 + /// + [ErrorCodeItemMetadata("检测数据不存在")] + xg1002, + + /// + /// 请添加数据列 + /// + [ErrorCodeItemMetadata("请添加数据列")] + db1000, + + /// + /// 数据表不存在 + /// + [ErrorCodeItemMetadata("数据表不存在")] + db1001, + + /// + /// 数据表不存在 + /// + [ErrorCodeItemMetadata("不允许添加相同字段名")] + db1002, + + /// + /// 父节点不存在 + /// + [ErrorCodeItemMetadata("父节点不存在")] + R2000, + + /// + /// 当前节点Id不能与父节点Id相同 + /// + [ErrorCodeItemMetadata("当前节点Id不能与父节点Id相同")] + R2001, + + /// + /// 已有相同编码或名称 + /// + [ErrorCodeItemMetadata("已有相同编码或名称")] + R2002, + + /// + /// 默认租户状态禁止修改 + /// + [ErrorCodeItemMetadata("默认租户状态禁止修改")] + Z1001, + + /// + /// 禁止创建此类型的数据库 + /// + [ErrorCodeItemMetadata("禁止创建此类型的数据库")] + Z1002, + + /// + /// 租户已禁用 + /// + [ErrorCodeItemMetadata("租户已禁用")] + Z1003, + + /// + /// 租户库连接不能为空 + /// + [ErrorCodeItemMetadata("租户库连接不能为空")] + Z1004, + + /// + /// 身份标识已存在 + /// + [ErrorCodeItemMetadata("身份标识已存在")] + O1000, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs new file mode 100644 index 00000000..3284532c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 性别枚举 +/// +[Description("性别枚举")] +public enum GenderEnum +{ + /// + /// 男 + /// + [Description("男")] + Male = 1, + + /// + /// 女 + /// + [Description("女")] + Female = 2, + + /// + /// 其他 + /// + [Description("其他")] + Other = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/HttpMethodEnum.cs b/Admin.NET/Admin.NET.Core/Enum/HttpMethodEnum.cs new file mode 100644 index 00000000..f9308931 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/HttpMethodEnum.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// HTTP请求方法枚举 +/// +[Description("HTTP请求方法枚举")] +public enum HttpMethodEnum +{ + /// + /// HTTP "GET" method. + /// + [Description("HTTP \"GET\" method.")] + Get, + + /// + /// HTTP "POST" method. + /// + [Description("HTTP \"POST\" method.")] + Post, + + /// + /// HTTP "PUT" method. + /// + [Description(" HTTP \"PUT\" method.")] + Put, + + /// + /// HTTP "DELETE" method. + /// + [Description("HTTP \"DELETE\" method.")] + Delete, + + /// + /// HTTP "PATCH" method. + /// + [Description("HTTP \"PATCH\" method. ")] + Patch, + + /// + /// HTTP "HEAD" method. + /// + [Description("HTTP \"HEAD\" method.")] + Head, + + /// + /// HTTP "OPTIONS" method. + /// + [Description("HTTP \"OPTIONS\" method.")] + Options, + + /// + /// HTTP "TRACE" method. + /// + [Description(" HTTP \"TRACE\" method.")] + Trace, + + /// + /// HTTP "CONNECT" method. + /// + [Description("HTTP \"CONNECT\" method.")] + Connect +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/JobCreateTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/JobCreateTypeEnum.cs new file mode 100644 index 00000000..acee68ba --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/JobCreateTypeEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 作业创建类型枚举 +/// +[Description("作业创建类型枚举")] +public enum JobCreateTypeEnum +{ + /// + /// 内置 + /// + [Description("内置")] + BuiltIn = 0, + + /// + /// 脚本 + /// + [Description("脚本")] + Script = 1, + + /// + /// HTTP请求 + /// + [Description("HTTP请求")] + Http = 2, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/JobStatusEnum.cs b/Admin.NET/Admin.NET.Core/Enum/JobStatusEnum.cs new file mode 100644 index 00000000..b06c65c5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/JobStatusEnum.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 岗位状态枚举 +/// +[Description("岗位状态枚举")] +public enum JobStatusEnum +{ + /// + /// 在职 + /// + [Description("在职")] + On = 1, + + /// + /// 离职 + /// + [Description("离职")] + Off = 2, + + /// + /// 请假 + /// + [Description("请假")] + Leave = 3, + + /// + /// 其他 + /// + [Description("其他")] + Other = 4, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/LoginModeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/LoginModeEnum.cs new file mode 100644 index 00000000..55e8ddb6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/LoginModeEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 登录模式枚举 +/// +[Description("登录模式枚举")] +public enum LoginModeEnum +{ + /// + /// PC模式 + /// + [Description("PC模式")] + PC = 1, + + /// + /// APP + /// + [Description("APP")] + APP = 2 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/LoginTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/LoginTypeEnum.cs new file mode 100644 index 00000000..9fa09502 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/LoginTypeEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 登录类型枚举 +/// +[Description("登录类型枚举")] +public enum LoginTypeEnum +{ + /// + /// PC登录 + /// + [Description("PC登录")] + Login = 1, + + /// + /// PC退出 + /// + [Description("PC退出")] + Logout = 2, + + /// + /// PC注册 + /// + [Description("PC注册")] + Register = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/MaritalStatusEnum.cs b/Admin.NET/Admin.NET.Core/Enum/MaritalStatusEnum.cs new file mode 100644 index 00000000..b3787b22 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/MaritalStatusEnum.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 婚姻状况枚举 +/// +[Description("婚姻状况枚举")] +public enum MaritalStatusEnum +{ + /// + /// 未婚 + /// + [Description("未婚")] + UnMarried = 1, + + /// + /// 已婚 + /// + [Description("已婚")] + Married = 2, + + /// + /// 离异 + /// + [Description("离异")] + Divorce = 3, + + /// + /// 再婚 + /// + [Description("再婚")] + Remarry = 4, + + /// + /// 丧偶 + /// + [Description("丧偶")] + Widowed = 5, + + /// + /// 未知 + /// + [Description("未知")] + None = 6, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/MenuTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/MenuTypeEnum.cs new file mode 100644 index 00000000..9d323216 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/MenuTypeEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统菜单类型枚举 +/// +[Description("系统菜单类型枚举")] +public enum MenuTypeEnum +{ + /// + /// 目录 + /// + [Description("目录")] + Dir = 1, + + /// + /// 菜单 + /// + [Description("菜单")] + Menu = 2, + + /// + /// 按钮 + /// + [Description("按钮")] + Btn = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/MessageTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/MessageTypeEnum.cs new file mode 100644 index 00000000..cfbe3a31 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/MessageTypeEnum.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 消息类型枚举 +/// +[Description("消息类型枚举")] +public enum MessageTypeEnum +{ + /// + /// 普通信息 + /// + [Description("消息")] + Info = 0, + + /// + /// 成功提示 + /// + [Description("成功")] + Success = 1, + + /// + /// 警告提示 + /// + [Description("警告")] + Warning = 2, + + /// + /// 错误提示 + /// + [Description("错误")] + Error = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/NoticeStatusEnum.cs b/Admin.NET/Admin.NET.Core/Enum/NoticeStatusEnum.cs new file mode 100644 index 00000000..16b4067a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/NoticeStatusEnum.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 通知公告状态枚举 +/// +[Description("通知公告状态枚举")] +public enum NoticeStatusEnum +{ + /// + /// 草稿 + /// + [Description("草稿")] + DRAFT = 0, + + /// + /// 发布 + /// + [Description("发布")] + PUBLIC = 1, + + /// + /// 撤回 + /// + [Description("撤回")] + CANCEL = 2, + + /// + /// 删除 + /// + [Description("删除")] + DELETED = 3 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/NoticeTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/NoticeTypeEnum.cs new file mode 100644 index 00000000..387bf29c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/NoticeTypeEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 通知公告状类型枚举 +/// +[Description("通知公告状类型枚举")] +public enum NoticeTypeEnum +{ + /// + /// 通知 + /// + [Description("通知")] + NOTICE = 1, + + /// + /// 公告 + /// + [Description("公告")] + ANNOUNCEMENT = 2, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/NoticeUserStatusEnum.cs b/Admin.NET/Admin.NET.Core/Enum/NoticeUserStatusEnum.cs new file mode 100644 index 00000000..91998b54 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/NoticeUserStatusEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 通知公告用户状态枚举 +/// +[Description("通知公告用户状态枚举")] +public enum NoticeUserStatusEnum +{ + /// + /// 未读 + /// + [Description("未读")] + UNREAD = 0, + + /// + /// 已读 + /// + [Description("已读")] + READ = 1 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/PlatformTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/PlatformTypeEnum.cs new file mode 100644 index 00000000..94727728 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/PlatformTypeEnum.cs @@ -0,0 +1,44 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 平台类型枚举 +/// +[Description("平台类型枚举")] +public enum PlatformTypeEnum +{ + /// + /// 微信公众号 + /// + [Description("微信公众号")] + 微信公众号 = 1, + + /// + /// 微信小程序 + /// + [Description("微信小程序")] + 微信小程序 = 2, + + /// + /// QQ + /// + [Description("QQ")] + QQ = 3, + + /// + /// Gitee + /// + [Description("Gitee")] + Gitee = 4, + + /// + /// 支付宝 + /// + [Description("支付宝")] + Alipay = 5, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/PrintTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/PrintTypeEnum.cs new file mode 100644 index 00000000..422972fa --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/PrintTypeEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 打印类型枚举 +/// +[Description("打印类型枚举")] +public enum PrintTypeEnum +{ + /// + /// 浏览器打印 + /// + [Description("浏览器打印")] + Browser = 1, + + /// + /// 浏览器打印 + /// + [Description("客户端打印")] + Client = 2, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/RequestTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/RequestTypeEnum.cs new file mode 100644 index 00000000..f1f4aee0 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/RequestTypeEnum.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// HTTP请求类型 +/// +[Description("HTTP请求类型")] +public enum RequestTypeEnum +{ + /// + /// 执行内部方法 + /// + Run = 0, + + /// + /// GET + /// + Get = 1, + + /// + /// POST + /// + Post = 2, + + /// + /// PUT + /// + Put = 3, + + /// + /// DELETE + /// + Delete = 4 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/StatusEnum.cs b/Admin.NET/Admin.NET.Core/Enum/StatusEnum.cs new file mode 100644 index 00000000..c9560353 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/StatusEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 通用状态枚举 +/// +[Description("通用状态枚举")] +public enum StatusEnum +{ + /// + /// 启用 + /// + [Description("启用")] + Enable = 1, + + /// + /// 停用 + /// + [Description("停用")] + Disable = 2, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/TenantTypeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/TenantTypeEnum.cs new file mode 100644 index 00000000..2873d0e4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/TenantTypeEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 租户类型枚举 +/// +[Description("租户类型枚举")] +public enum TenantTypeEnum +{ + /// + /// Id隔离 + /// + [Description("Id隔离")] + Id = 0, + + /// + /// 库隔离 + /// + [Description("库隔离")] + Db = 1, +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/WechatReturnCodeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/WechatReturnCodeEnum.cs new file mode 100644 index 00000000..ba30a3e1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/WechatReturnCodeEnum.cs @@ -0,0 +1,289 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 微信开发返回码 +/// +[Description("微信开发返回码")] +public enum WechatReturnCodeEnum +{ + SenparcWeixinSDK配置错误 = -99, // 0xFFFFFF9D + 系统繁忙此时请开发者稍候再试 = -1, // 0xFFFFFFFF + 请求成功 = 0, + 工商数据返回_企业已注销 = 101, // 0x00000065 + 工商数据返回_企业不存在或企业信息未更新 = 102, // 0x00000066 + 工商数据返回_企业法定代表人姓名不一致 = 103, // 0x00000067 + 工商数据返回_企业法定代表人身份证号码不一致 = 104, // 0x00000068 + 法定代表人身份证号码_工商数据未更新_请5_15个工作日之后尝试 = 105, // 0x00000069 + 工商数据返回_企业信息或法定代表人信息不一致 = 1000, // 0x000003E8 + 对方不是粉丝 = 10700, // 0x000029CC + 发送消息失败_对方关闭了接收消息 = 10703, // 0x000029CF + 发送消息失败_48小时内用户未互动 = 10706, // 0x000029D2 + POST参数非法 = 20002, // 0x00004E22 + 获取access_token时AppSecret错误或者access_token无效 = 40001, // 0x00009C41 + + /// + /// 公众号:不合法的凭证类型 + /// 小程序:暂无生成权限 + /// + 不合法的凭证类型 = 40002, // 0x00009C42 + + 不合法的OpenID = 40003, // 0x00009C43 + 不合法的媒体文件类型 = 40004, // 0x00009C44 + 不合法的文件类型 = 40005, // 0x00009C45 + 不合法的文件大小 = 40006, // 0x00009C46 + 不合法的媒体文件id = 40007, // 0x00009C47 + 不合法的消息类型_40008 = 40008, // 0x00009C48 + 不合法的图片文件大小 = 40009, // 0x00009C49 + 不合法的语音文件大小 = 40010, // 0x00009C4A + 不合法的视频文件大小 = 40011, // 0x00009C4B + 不合法的缩略图文件大小 = 40012, // 0x00009C4C + + /// + /// 微信:不合法的APPID + /// 小程序:生成权限被封禁 + /// + 不合法的APPID = 40013, // 0x00009C4D + + 不合法的access_token = 40014, // 0x00009C4E + 不合法的菜单类型 = 40015, // 0x00009C4F + 不合法的按钮个数1 = 40016, // 0x00009C50 + 不合法的按钮个数2 = 40017, // 0x00009C51 + 不合法的按钮名字长度 = 40018, // 0x00009C52 + 不合法的按钮KEY长度 = 40019, // 0x00009C53 + 不合法的按钮URL长度 = 40020, // 0x00009C54 + 不合法的菜单版本号 = 40021, // 0x00009C55 + 不合法的子菜单级数 = 40022, // 0x00009C56 + 不合法的子菜单按钮个数 = 40023, // 0x00009C57 + 不合法的子菜单按钮类型 = 40024, // 0x00009C58 + 不合法的子菜单按钮名字长度 = 40025, // 0x00009C59 + 不合法的子菜单按钮KEY长度 = 40026, // 0x00009C5A + 不合法的子菜单按钮URL长度 = 40027, // 0x00009C5B + 不合法的自定义菜单使用用户 = 40028, // 0x00009C5C + 不合法的oauth_code = 40029, // 0x00009C5D + 不合法的refresh_token = 40030, // 0x00009C5E + 不合法的openid列表 = 40031, // 0x00009C5F + 不合法的openid列表长度 = 40032, // 0x00009C60 + 不合法的请求字符不能包含uxxxx格式的字符 = 40033, // 0x00009C61 + 不合法的参数 = 40035, // 0x00009C63 + template_id不正确 = 40037, // 0x00009C65 + 不合法的请求格式 = 40038, // 0x00009C66 + 不合法的URL长度 = 40039, // 0x00009C67 + 不合法的分组id = 40050, // 0x00009C72 + 分组名字不合法 = 40051, // 0x00009C73 + + /// + /// 公众号:输入参数有误 + /// 小程序:参数expire_time填写错误 + /// + 输入参数有误 = 40097, // 0x00009CA1 + + appsecret不正确 = 40125, // 0x00009CBD + 调用接口的IP地址不在白名单中 = 40164, // 0x00009CE4 + 参数path填写错误 = 40165, // 0x00009CE5 + 小程序Appid不存在 = 40166, // 0x00009CE6 + 参数query填写错误 = 40212, // 0x00009D14 + 缺少access_token参数 = 41001, // 0x0000A029 + 缺少appid参数 = 41002, // 0x0000A02A + 缺少refresh_token参数 = 41003, // 0x0000A02B + 缺少secret参数 = 41004, // 0x0000A02C + 缺少多媒体文件数据 = 41005, // 0x0000A02D + 缺少media_id参数 = 41006, // 0x0000A02E + 缺少子菜单数据 = 41007, // 0x0000A02F + 缺少oauth_code = 41008, // 0x0000A030 + 缺少openid = 41009, // 0x0000A031 + form_id不正确_或者过期 = 41028, // 0x0000A044 + form_id已被使用 = 41029, // 0x0000A045 + page不正确 = 41030, // 0x0000A046 + access_token超时 = 42001, // 0x0000A411 + refresh_token超时 = 42002, // 0x0000A412 + oauth_code超时 = 42003, // 0x0000A413 + 需要GET请求 = 43001, // 0x0000A7F9 + 需要POST请求 = 43002, // 0x0000A7FA + 需要HTTPS请求 = 43003, // 0x0000A7FB + 需要接收者关注 = 43004, // 0x0000A7FC + 需要好友关系 = 43005, // 0x0000A7FD + + /// [小程序订阅消息]用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系 + 用户拒绝接受消息 = 43101, // 0x0000A85D + + 没有权限 = 43104, // 0x0000A860 + 多媒体文件为空 = 44001, // 0x0000ABE1 + POST的数据包为空 = 44002, // 0x0000ABE2 + 图文消息内容为空 = 44003, // 0x0000ABE3 + 文本消息内容为空 = 44004, // 0x0000ABE4 + 多媒体文件大小超过限制 = 45001, // 0x0000AFC9 + 消息内容超过限制 = 45002, // 0x0000AFCA + 标题字段超过限制 = 45003, // 0x0000AFCB + 描述字段超过限制 = 45004, // 0x0000AFCC + 链接字段超过限制 = 45005, // 0x0000AFCD + 图片链接字段超过限制 = 45006, // 0x0000AFCE + 语音播放时间超过限制 = 45007, // 0x0000AFCF + 图文消息超过限制 = 45008, // 0x0000AFD0 + 接口调用超过限制 = 45009, // 0x0000AFD1 + 创建菜单个数超过限制 = 45010, // 0x0000AFD2 + 回复时间超过限制 = 45015, // 0x0000AFD7 + 系统分组不允许修改 = 45016, // 0x0000AFD8 + 分组名字过长 = 45017, // 0x0000AFD9 + 分组数量超过上限 = 45018, // 0x0000AFDA + 超出响应数量限制 = 45047, // 0x0000AFF7 + 创建的标签数过多请注意不能超过100个 = 45056, // 0x0000B000 + 标签名非法请注意不能和其他标签重名 = 45157, // 0x0000B065 + 标签名长度超过30个字节 = 45158, // 0x0000B066 + 不存在媒体数据 = 46001, // 0x0000B3B1 + 不存在的菜单版本 = 46002, // 0x0000B3B2 + 不存在的菜单数据 = 46003, // 0x0000B3B3 + 解析JSON_XML内容错误 = 47001, // 0x0000B799 + + /// [小程序订阅消息]模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错 + 模板参数不准确 = 47003, // 0x0000B79B + + api功能未授权 = 48001, // 0x0000BB81 + 用户未授权该api = 50001, // 0x0000C351 + 名称格式不合法 = 53010, // 0x0000CF12 + 名称检测命中频率限制 = 53011, // 0x0000CF13 + 禁止使用该名称 = 53012, // 0x0000CF14 + 公众号_名称与已有公众号名称重复_小程序_该名称与已有小程序名称重复 = 53013, // 0x0000CF15 + 公众号_公众号已有_名称A_时_需与该帐号相同主体才可申请_名称A_小程序_小程序已有_名称A_时_需与该帐号相同主体才可申请_名称A_ = 53014, // 0x0000CF16 + 公众号_该名称与已有小程序名称重复_需与该小程序帐号相同主体才可申请_小程序_该名称与已有公众号名称重复_需与该公众号帐号相同主体才可申请 = 53015, // 0x0000CF17 + 公众号_该名称与已有多个小程序名称重复_暂不支持申请_小程序_该名称与已有多个公众号名称重复_暂不支持申请 = 53016, // 0x0000CF18 + 公众号_小程序已有_名称A_时_需与该帐号相同主体才可申请_名称A_小程序_公众号已有_名称A_时_需与该帐号相同主体才可申请_名称A = 53017, // 0x0000CF19 + 名称命中微信号 = 53018, // 0x0000CF1A + 名称在保护期内 = 53019, // 0x0000CF1B + 法人姓名与微信号不一致 = 61070, // 0x0000EE8E + 系统错误system_error = 61450, // 0x0000F00A + 参数错误invalid_parameter = 61451, // 0x0000F00B + 无效客服账号invalid_kf_account = 61452, // 0x0000F00C + 客服帐号已存在kf_account_exsited = 61453, // 0x0000F00D + + /// + /// 客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length) + /// + 客服帐号名长度超过限制 = 61454, // 0x0000F00E + + /// + /// 客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account) + /// + 客服帐号名包含非法字符 = 61455, // 0x0000F00F + + /// 客服帐号个数超过限制(10个客服账号)(kf_account count exceeded) + 客服帐号个数超过限制 = 61456, // 0x0000F010 + + 无效头像文件类型invalid_file_type = 61457, // 0x0000F011 + 日期格式错误 = 61500, // 0x0000F03C + 日期范围错误 = 61501, // 0x0000F03D + 发送消息失败_该用户已被加入黑名单_无法向此发送消息 = 62751, // 0x0000F51F + 门店不存在 = 65115, // 0x0000FE5B + 该门店状态不允许更新 = 65118, // 0x0000FE5E + 标签格式错误 = 85006, // 0x00014C0E + 页面路径错误 = 85007, // 0x00014C0F + 类目填写错误 = 85008, // 0x00014C10 + 已经有正在审核的版本 = 85009, // 0x00014C11 + item_list有项目为空 = 85010, // 0x00014C12 + 标题填写错误 = 85011, // 0x00014C13 + 无效的审核id = 85012, // 0x00014C14 + 版本输入错误 = 85015, // 0x00014C17 + 没有审核版本 = 85019, // 0x00014C1B + 审核状态未满足发布 = 85020, // 0x00014C1C + 状态不可变 = 85021, // 0x00014C1D + action非法 = 85022, // 0x00014C1E + 审核列表填写的项目数不在1到5以内 = 85023, // 0x00014C1F + 需要补充相应资料_填写org_code和other_files参数 = 85024, // 0x00014C20 + 管理员手机登记数量已超过上限 = 85025, // 0x00014C21 + 该微信号已绑定5个管理员 = 85026, // 0x00014C22 + 管理员身份证已登记过5次 = 85027, // 0x00014C23 + 该主体登记数量已超过上限 = 85028, // 0x00014C24 + 商家名称已被占用 = 85029, // 0x00014C25 + 不能使用该名称 = 85031, // 0x00014C27 + 该名称在侵权投诉保护期 = 85032, // 0x00014C28 + 名称包含违规内容或微信等保留字 = 85033, // 0x00014C29 + 商家名称在改名15天保护期内 = 85034, // 0x00014C2A + 需与该帐号相同主体才可申请 = 85035, // 0x00014C2B + 介绍中含有虚假混淆内容 = 85036, // 0x00014C2C + 头像或者简介修改达到每个月上限 = 85049, // 0x00014C39 + 正在审核中_请勿重复提交 = 85050, // 0x00014C3A + 请先成功创建门店后再调用 = 85053, // 0x00014C3D + 临时mediaid无效 = 85056, // 0x00014C40 + 链接错误 = 85066, // 0x00014C4A + 测试链接不是子链接 = 85068, // 0x00014C4C + 校验文件失败 = 85069, // 0x00014C4D + 个人类型小程序无法设置二维码规则 = 85070, // 0x00014C4E + 已添加该链接_请勿重复添加 = 85071, // 0x00014C4F + 该链接已被占用 = 85072, // 0x00014C50 + 二维码规则已满 = 85073, // 0x00014C51 + 小程序未发布_小程序必须先发布代码才可以发布二维码跳转规则 = 85074, // 0x00014C52 + 个人类型小程序无法设置二维码规则1 = 85075, // 0x00014C53 + 小程序没有线上版本_不能进行灰度 = 85079, // 0x00014C57 + 小程序提交的审核未审核通过 = 85080, // 0x00014C58 + 无效的发布比例 = 85081, // 0x00014C59 + 当前的发布比例需要比之前设置的高 = 85082, // 0x00014C5A + 小程序提审数量已达本月上限 = 85085, // 0x00014C5D + 提交代码审核之前需提前上传代码 = 85086, // 0x00014C5E + 小程序已使用_api_navigateToMiniProgram_请声明跳转_appid_列表后再次提交 = 85087, // 0x00014C5F + 不是由第三方代小程序进行调用 = 86000, // 0x00014FF0 + 不存在第三方的已经提交的代码 = 86001, // 0x00014FF1 + 小程序还未设置昵称_头像_简介_请先设置完后再重新提交 = 86002, // 0x00014FF2 + 无效微信号 = 86004, // 0x00014FF4 + + /// + /// 小程序为“签名错误”。对应公众号: 87009, “errmsg” : “reply is not exists” //该回复不存在 + /// + 签名错误 = 87009, // 0x000153E1 + + 现网已经在灰度发布_不能进行版本回退 = 87011, // 0x000153E3 + 该版本不能回退_可能的原因_1_无上一个线上版用于回退_2_此版本为已回退版本_不能回退_3_此版本为回退功能上线之前的版本_不能回退 = 87012, // 0x000153E4 + 内容含有违法违规内容 = 87014, // 0x000153E6 + 没有留言权限 = 88000, // 0x000157C0 + 该图文不存在 = 88001, // 0x000157C1 + 文章存在敏感信息 = 88002, // 0x000157C2 + 精选评论数已达上限 = 88003, // 0x000157C3 + 已被用户删除_无法精选 = 88004, // 0x000157C4 + 已经回复过了 = 88005, // 0x000157C5 + 回复超过长度限制或为0 = 88007, // 0x000157C7 + 该评论不存在 = 88008, // 0x000157C8 + 获取评论数目不合法 = 88010, // 0x000157CA + 该公众号_小程序已经绑定了开放平台帐号 = 89000, // 0x00015BA8 + 业务域名无更改_无需重复设置 = 89019, // 0x00015BBB + 尚未设置小程序业务域名_请先在第三方平台中设置小程序业务域名后在调用本接口 = 89020, // 0x00015BBC + 请求保存的域名不是第三方平台中已设置的小程序业务域名或子域名 = 89021, // 0x00015BBD + 业务域名数量超过限制_最多可以添加100个业务域名 = 89029, // 0x00015BC5 + 个人小程序不支持调用_setwebviewdomain_接口 = 89231, // 0x00015C8F + 内部错误 = 89247, // 0x00015C9F + 企业代码类型无效_请选择正确类型填写 = 89248, // 0x00015CA0 + 该主体已有任务执行中_距上次任务24h后再试 = 89249, // 0x00015CA1 + 未找到该任务 = 89250, // 0x00015CA2 + 待法人人脸核身校验 = 89251, // 0x00015CA3 + 法人_企业信息一致性校验中 = 89252, // 0x00015CA4 + 缺少参数 = 89253, // 0x00015CA5 + 第三方权限集不全_补全权限集全网发布后生效 = 89254, // 0x00015CA6 + 系统不稳定_请稍后再试_如多次失败请通过社区反馈 = 89401, // 0x00015D39 + 该审核单不在待审核队列_请检查是否已提交审核或已审完 = 89402, // 0x00015D3A + 本单属于平台不支持加急种类_请等待正常审核流程 = 89403, // 0x00015D3B + 本单已加速成功_请勿重复提交 = 89404, // 0x00015D3C + 本月加急额度不足_请提升提审质量以获取更多额度 = 89405, // 0x00015D3D + 该经营资质已添加_请勿重复添加 = 92000, // 0x00016760 + 附近地点添加数量达到上线_无法继续添加 = 92002, // 0x00016762 + 地点已被其它小程序占用 = 92003, // 0x00016763 + 附近功能被封禁 = 92004, // 0x00016764 + 地点正在审核中 = 92005, // 0x00016765 + 地点正在展示小程序 = 92006, // 0x00016766 + 地点审核失败 = 92007, // 0x00016767 + 程序未展示在该地点 = 92008, // 0x00016768 + 小程序未上架或不可见 = 92009, // 0x00016769 + 地点不存在 = 93010, // 0x00016B52 + 个人类型小程序不可用 = 93011, // 0x00016B53 + 已下发的模板消息法人并未确认且已超时_24h_未进行身份证校验 = 100001, // 0x000186A1 + 已下发的模板消息法人并未确认且已超时_24h_未进行人脸识别校验 = 100002, // 0x000186A2 + 已下发的模板消息法人并未确认且已超时_24h = 100003, // 0x000186A3 + 此账号已被封禁_无法操作 = 200011, // 0x00030D4B + 私有模板数已达上限_上限_50_个 = 200012, // 0x00030D4C + 此模版已被封禁_无法选用 = 200013, // 0x00030D4D + 模版tid参数错误 = 200014, // 0x00030D4E + 关键词列表kidList参数错误 = 200020, // 0x00030D54 + 场景描述sceneDesc参数错误 = 200021, // 0x00030D55 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/YesNoEnum.cs b/Admin.NET/Admin.NET.Core/Enum/YesNoEnum.cs new file mode 100644 index 00000000..b241a678 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/YesNoEnum.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 是否枚举 +/// +[Description("是否枚举")] +public enum YesNoEnum +{ + /// + /// 是 + /// + [Description("是")] + Y = 1, + + /// + /// 否 + /// + [Description("否")] + N = 2 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs b/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs new file mode 100644 index 00000000..2a438056 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 事件订阅 +/// +public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable +{ + private readonly IServiceScope _serviceScope; + + public AppEventSubscriber(IServiceScopeFactory scopeFactory) + { + _serviceScope = scopeFactory.CreateScope(); + } + + /// + /// 增加异常日志 + /// + /// + /// + [EventSubscribe(CommonConst.AddExLog)] + public async Task CreateExLog(EventHandlerExecutingContext context) + { + var rep = _serviceScope.ServiceProvider.GetRequiredService>(); + await rep.InsertAsync((SysLogEx)context.Source.Payload); + } + + /// + /// 发送异常邮件 + /// + /// + /// + [EventSubscribe(CommonConst.SendErrorMail)] + public async Task SendOrderErrorMail(EventHandlerExecutingContext context) + { + //var mailTempPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Temp\\ErrorMail.tp"); + //var mailTemp = File.ReadAllText(mailTempPath); + //var mail = await _serviceScope.ServiceProvider.GetRequiredService().RunCompileFromCachedAsync(mailTemp, ); + + var title = "Admin.NET 系统异常"; + await _serviceScope.ServiceProvider.GetRequiredService().SendEmail(JSON.Serialize(context.Source.Payload), title); + } + + /// + /// 释放服务作用域 + /// + public void Dispose() + { + _serviceScope.Dispose(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs b/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs new file mode 100644 index 00000000..595bc891 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs @@ -0,0 +1,112 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// Redis 消息扩展 +/// +/// +public class EventConsumer : IDisposable +{ + private Task _consumerTask; + private CancellationTokenSource _consumerCts; + + /// + /// 消费者 + /// + public IProducerConsumer Consumer { get; } + + /// + /// ConsumerBuilder + /// + public FullRedis Builder { get; set; } + + /// + /// 消息回调 + /// + public event EventHandler Received; + + /// + /// 构造函数 + /// + public EventConsumer(FullRedis redis, string routeKey) + { + Builder = redis; + Consumer = Builder.GetQueue(routeKey); + } + + /// + /// 启动 + /// + /// + public void Start() + { + if (Consumer is null) + { + throw new InvalidOperationException("Subscribe first using the Consumer.Subscribe() function"); + } + if (_consumerTask != null) + { + return; + } + _consumerCts = new CancellationTokenSource(); + var ct = _consumerCts.Token; + _consumerTask = Task.Factory.StartNew(() => + { + while (!ct.IsCancellationRequested) + { + var cr = Consumer.TakeOne(10); + if (cr == null) continue; + Received?.Invoke(this, cr); + } + }, ct, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + /// + /// 停止 + /// + /// + public async Task Stop() + { + if (_consumerCts == null || _consumerTask == null) return; + _consumerCts.Cancel(); + try + { + await _consumerTask; + } + finally + { + _consumerTask = null; + _consumerCts = null; + } + } + + /// + /// 释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 释放 + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_consumerTask != null) + { + Stop().Wait(); + } + Builder.Dispose(); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/RabbitMQEventSourceStore.cs b/Admin.NET/Admin.NET.Core/EventBus/RabbitMQEventSourceStore.cs new file mode 100644 index 00000000..0372eb0c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/RabbitMQEventSourceStore.cs @@ -0,0 +1,132 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System.Threading.Channels; + +namespace Admin.NET.Core; + +/// +/// RabbitMQ自定义事件源存储器 +/// +public class RabbitMQEventSourceStore : IEventSourceStorer +{ + /// + /// 内存通道事件源存储器 + /// + private readonly Channel _channel; + + /// + /// 通道对象 + /// + private readonly IModel _model; + + /// + /// 连接对象 + /// + private readonly IConnection _connection; + + /// + /// 路由键 + /// + private readonly string _routeKey; + + /// + /// 构造函数 + /// + /// 连接工厂 + /// 路由键 + /// 存储器最多能够处理多少消息,超过该容量进入等待写入 + public RabbitMQEventSourceStore(ConnectionFactory factory, string routeKey, int capacity) + { + // 配置通道,设置超出默认容量后进入等待 + var boundedChannelOptions = new BoundedChannelOptions(capacity) + { + FullMode = BoundedChannelFullMode.Wait + }; + + // 创建有限容量通道 + _channel = Channel.CreateBounded(boundedChannelOptions); + + // 创建连接 + _connection = factory.CreateConnection(); + _routeKey = routeKey; + + // 创建通道 + _model = _connection.CreateModel(); + + // 声明路由队列 + _model.QueueDeclare(routeKey, false, false, false, null); + + // 创建消息订阅者 + var consumer = new EventingBasicConsumer(_model); + + // 订阅消息并写入内存 Channel + consumer.Received += (ch, ea) => + { + // 读取原始消息 + var stringEventSource = Encoding.UTF8.GetString(ea.Body.ToArray()); + + // 转换为 IEventSource,如果自定义了 EventSource,注意属性是可读可写 + var eventSource = JSON.Deserialize(stringEventSource); + + // 写入内存管道存储器 + _channel.Writer.WriteAsync(eventSource); + + // 确认该消息已被消费 + _model.BasicAck(ea.DeliveryTag, false); + }; + + // 启动消费者且设置为手动应答消息 + _model.BasicConsume(routeKey, false, consumer); + } + + /// + /// 将事件源写入存储器 + /// + /// 事件源对象 + /// 取消任务 Token + /// + public async ValueTask WriteAsync(IEventSource eventSource, CancellationToken cancellationToken) + { + if (eventSource == default) + throw new ArgumentNullException(nameof(eventSource)); + + // 判断是否是 ChannelEventSource 或自定义的 EventSource + if (eventSource is ChannelEventSource source) + { + // 序列化及发布 + var data = Encoding.UTF8.GetBytes(JSON.Serialize(source)); + _model.BasicPublish("", _routeKey, null, data); + } + else + { + // 处理动态订阅 + await _channel.Writer.WriteAsync(eventSource, cancellationToken); + } + } + + /// + /// 从存储器中读取一条事件源 + /// + /// 取消任务 Token + /// 事件源对象 + public async ValueTask ReadAsync(CancellationToken cancellationToken) + { + var eventSource = await _channel.Reader.ReadAsync(cancellationToken); + return eventSource; + } + + /// + /// 释放非托管资源 + /// + public void Dispose() + { + _model.Dispose(); + _connection.Dispose(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs b/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs new file mode 100644 index 00000000..975a3dc3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs @@ -0,0 +1,132 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Threading.Channels; + +namespace Admin.NET.Core; + +/// +/// Redis自定义事件源存储器 +/// +public sealed class RedisEventSourceStorer : IEventSourceStorer, IDisposable +{ + /// + /// 消费者 + /// + private readonly EventConsumer _eventConsumer; + + /// + /// 内存通道事件源存储器 + /// + private readonly Channel _channel; + + /// + /// Redis 连接对象 + /// + private readonly FullRedis _redis; + + /// + /// 路由键 + /// + private readonly string _routeKey; + + /// + /// 构造函数 + /// + /// Redis 连接对象 + /// 路由键 + /// 存储器最多能够处理多少消息,超过该容量进入等待写入 + public RedisEventSourceStorer(ICache redis, string routeKey, int capacity) + { + // 配置通道,设置超出默认容量后进入等待 + var boundedChannelOptions = new BoundedChannelOptions(capacity) + { + FullMode = BoundedChannelFullMode.Wait + }; + + // 创建有限容量通道 + _channel = Channel.CreateBounded(boundedChannelOptions); + + _redis = redis as FullRedis; + _routeKey = routeKey; + + // 创建消息订阅者 + _eventConsumer = new EventConsumer(_redis, _routeKey); + + // 订阅消息写入 Channel + _eventConsumer.Received += (send, cr) => + { + // 反序列化消息 + //var eventSource = JsonConvert.DeserializeObject(cr); + + // 写入内存管道存储器 + Task.Run(async () => + { + await _channel.Writer.WriteAsync(cr); + }); + }; + + // 启动消费者 + _eventConsumer.Start(); + } + + /// + /// 将事件源写入存储器 + /// + /// 事件源对象 + /// 取消任务 Token + /// + public async ValueTask WriteAsync(IEventSource eventSource, CancellationToken cancellationToken) + { + // 空检查 + if (eventSource == default) + { + throw new ArgumentNullException(nameof(eventSource)); + } + + // 这里判断是否是 ChannelEventSource 或者 自定义的 EventSource + if (eventSource is ChannelEventSource source) + { + // 序列化消息 + //var data = JsonSerializer.Serialize(source); + + // 获取一个订阅对象 + var queue = _redis.GetQueue(_routeKey); + + // 异步发布 + await Task.Factory.StartNew(() => + { + queue.Add(source); + }, cancellationToken, TaskCreationOptions.LongRunning, System.Threading.Tasks.TaskScheduler.Default); + } + else + { + // 这里处理动态订阅问题 + await _channel.Writer.WriteAsync(eventSource, cancellationToken); + } + } + + /// + /// 从存储器中读取一条事件源 + /// + /// 取消任务 Token + /// 事件源对象 + public async ValueTask ReadAsync(CancellationToken cancellationToken) + { + // 读取一条事件源 + var eventSource = await _channel.Reader.ReadAsync(cancellationToken); + return eventSource; + } + + /// + /// 释放非托管资源 + /// + public async void Dispose() + { + await _eventConsumer.Stop(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/RedisQueue.cs b/Admin.NET/Admin.NET.Core/EventBus/RedisQueue.cs new file mode 100644 index 00000000..ee44149c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/RedisQueue.cs @@ -0,0 +1,241 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using NewLife.Caching.Queues; + +namespace Admin.NET.Core; + +/// +/// Redis 消息队列 +/// +public static class RedisQueue +{ + private static readonly ICache _cache = App.GetRequiredService(); + + /// + /// 获取普通队列 + /// + /// + /// + /// + public static IProducerConsumer GetQueue(string topic) + { + var queue = (_cache as FullRedis).GetQueue(topic); + return queue; + } + + /// + /// 发送一个数据到队列 + /// + /// + /// + /// + /// + public static int AddQueue(string topic, T value) + { + var queue = GetQueue(topic); + return queue.Add(value); + } + + /// + /// 发送一个数据列表到队列 + /// + /// + /// + /// + /// + public static int AddQueueList(string topic, List value) + { + var queue = GetQueue(topic); + var count = queue.Count; + var result = queue.Add(value.ToArray()); + return result - count; + } + + /// + /// 获取一批队列消息 + /// + /// + /// + /// + /// + public static List Take(string topic, int count = 1) + { + var queue = GetQueue(topic); + var result = queue.Take(count).ToList(); + return result; + } + + /// + /// 获取一个队列消息 + /// + /// + /// + /// + public static async Task TakeOneAsync(string topic) + { + var queue = GetQueue(topic); + return await queue.TakeOneAsync(1); + } + + /// + /// 获取可信队列,需要确认 + /// + /// + /// + /// + public static RedisReliableQueue GetRedisReliableQueue(string topic) + { + var queue = (_cache as FullRedis).GetReliableQueue(topic); + return queue; + } + + /// + /// 可信队列回滚 + /// + /// + /// + /// + public static int RollbackAllAck(string topic, int retryInterval = 60) + { + var queue = GetRedisReliableQueue(topic); + queue.RetryInterval = retryInterval; + return queue.RollbackAllAck(); + } + + /// + /// 发送一个数据列表到可信队列 + /// + /// + /// + /// + /// + public static int AddReliableQueueList(string topic, List value) + { + var queue = (_cache as FullRedis).GetReliableQueue(topic); + var count = queue.Count; + var result = queue.Add(value.ToArray()); + return result - count; + } + + /// + /// 发送一条数据到可信队列 + /// + /// + /// + /// + /// + public static int AddReliableQueue(string topic, T value) + { + var queue = (_cache as FullRedis).GetReliableQueue(topic); + var count = queue.Count; + var result = queue.Add(value); + return result - count; + } + + /// + /// 在可信队列获取一条数据 + /// + /// + /// + /// + public static T ReliableTakeOne(string topic) + { + var queue = GetRedisReliableQueue(topic); + return queue.TakeOne(1); + } + + /// + /// 异步在可信队列获取一条数据 + /// + /// + /// + /// + public static async Task ReliableTakeOneAsync(string topic) + { + var queue = GetRedisReliableQueue(topic); + return await queue.TakeOneAsync(1); + } + + /// + /// 在可信队列获取多条数据 + /// + /// + /// + /// + /// + public static List ReliableTake(string topic, int count) + { + var queue = GetRedisReliableQueue(topic); + return queue.Take(count).ToList(); + } + + /// + /// 获取延迟队列 + /// + /// + /// + /// + public static RedisDelayQueue GetDelayQueue(string topic) + { + var queue = (_cache as FullRedis).GetDelayQueue(topic); + return queue; + } + + /// + /// 发送一条数据到延迟队列 + /// + /// + /// + /// 延迟时间。单位秒 + /// + /// + public static int AddDelayQueue(string topic, T value, int delay) + { + var queue = GetDelayQueue(topic); + return queue.Add(value, delay); + } + + /// + /// 发送数据列表到延迟队列 + /// + /// + /// + /// + /// 延迟时间。单位秒 + /// + public static int AddDelayQueue(string topic, List value, int delay) + { + var queue = GetDelayQueue(topic); + queue.Delay = delay; + return queue.Add(value.ToArray()); + } + + /// + /// 异步在延迟队列获取一条数据 + /// + /// + /// + /// + public static async Task DelayTakeOne(string topic) + { + var queue = GetDelayQueue(topic); + return await queue.TakeOneAsync(1); + } + + /// + /// 在延迟队列获取多条数据 + /// + /// + /// + /// + /// + public static List DelayTake(string topic, int count = 1) + { + var queue = GetDelayQueue(topic); + return queue.Take(count).ToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/RetryEventHandlerExecutor.cs b/Admin.NET/Admin.NET.Core/EventBus/RetryEventHandlerExecutor.cs new file mode 100644 index 00000000..f53f5756 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/EventBus/RetryEventHandlerExecutor.cs @@ -0,0 +1,22 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 事件执行器-超时控制、失败重试熔断等等 +/// +public class RetryEventHandlerExecutor : IEventHandlerExecutor +{ + public async Task ExecuteAsync(EventHandlerExecutingContext context, Func handler) + { + // 如果执行失败,每隔 1s 重试,最多三次 + await Retry.InvokeAsync(async () => + { + await handler(context); + }, 3, 1000); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/ConsoleLogoSetup.cs b/Admin.NET/Admin.NET.Core/Extension/ConsoleLogoSetup.cs new file mode 100644 index 00000000..be20e972 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/ConsoleLogoSetup.cs @@ -0,0 +1,27 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 控制台logo +/// +public static class ConsoleLogoSetup +{ + public static void AddConsoleLogo(this IServiceCollection services) + { + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine(@" + _ _ _ _ ______ _______ + /\ | | (_) | \ | | ____|__ __| + / \ __| |_ __ ___ _ _ __ | \| | |__ | | + / /\ \ / _` | '_ ` _ \| | '_ \ | . ` | __| | | + / ____ \ (_| | | | | | | | | | |_| |\ | |____ | | + /_/ \_\__,_|_| |_| |_|_|_| |_(_)_| \_|______| |_| "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(@"让.NET更简单、更通用、更流行!"); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs b/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs new file mode 100644 index 00000000..89447cfe --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs @@ -0,0 +1,225 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 枚举拓展 +/// +public static class EnumExtension +{ + // 枚举显示字典缓存 + private static readonly ConcurrentDictionary> EnumDisplayValueDict = new(); + + // 枚举值字典缓存 + private static readonly ConcurrentDictionary> EnumNameValueDict = new(); + + // 枚举类型缓存 + private static ConcurrentDictionary _enumTypeDict; + + /// + /// 获取枚举对象Key与名称的字典(缓存) + /// + /// + /// + public static Dictionary GetEnumDictionary(this Type enumType) + { + if (!enumType.IsEnum) + throw new ArgumentException("Type '" + enumType.Name + "' is not an enum."); + + // 查询缓存 + var enumDic = EnumNameValueDict.ContainsKey(enumType) ? EnumNameValueDict[enumType] : new Dictionary(); + if (enumDic.Count != 0) + return enumDic; + // 取枚举类型的Key/Value字典集合 + enumDic = GetEnumDictionaryItems(enumType); + + // 缓存 + EnumNameValueDict[enumType] = enumDic; + + return enumDic; + } + + /// + /// 获取枚举对象Key与名称的字典 + /// + /// + /// + private static Dictionary GetEnumDictionaryItems(this Type enumType) + { + // 获取类型的字段,初始化一个有限长度的字典 + var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + Dictionary enumDic = new(enumFields.Length); + + // 遍历字段数组获取key和name + foreach (var enumField in enumFields) + { + var intValue = (int)enumField.GetValue(enumType); + enumDic[intValue] = enumField.Name; + } + + return enumDic; + } + + /// + /// 获取枚举类型key与描述的字典(缓存) + /// + /// + /// + /// + public static Dictionary GetEnumDescDictionary(this Type enumType) + { + if (!enumType.IsEnum) + throw new ArgumentException("Type '" + enumType.Name + "' is not an enum."); + + // 查询缓存 + var enumDic = EnumDisplayValueDict.ContainsKey(enumType) + ? EnumDisplayValueDict[enumType] + : new Dictionary(); + if (enumDic.Count != 0) + return enumDic; + // 取枚举类型的Key/Value字典集合 + enumDic = GetEnumDescDictionaryItems(enumType); + + // 缓存 + EnumDisplayValueDict[enumType] = enumDic; + + return enumDic; + } + + /// + /// 获取枚举类型key与描述的字典(没有描述则获取name) + /// + /// + /// + /// + private static Dictionary GetEnumDescDictionaryItems(this Type enumType) + { + // 获取类型的字段,初始化一个有限长度的字典 + var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + Dictionary enumDic = new(enumFields.Length); + + // 遍历字段数组获取key和name + foreach (var enumField in enumFields) + { + var intValue = (int)enumField.GetValue(enumType); + var desc = enumField.GetDescriptionValue(); + enumDic[intValue] = desc != null && !string.IsNullOrEmpty(desc.Description) ? desc.Description : enumField.Name; + } + + return enumDic; + } + + /// + /// 从程序集中查找指定枚举类型 + /// + /// + /// + /// + public static Type TryToGetEnumType(Assembly assembly, string typeName) + { + // 枚举缓存为空则重新加载枚举类型字典 + _enumTypeDict ??= LoadEnumTypeDict(assembly); + + // 按名称查找 + return _enumTypeDict.ContainsKey(typeName) ? _enumTypeDict[typeName] : null; + } + + /// + /// 从程序集中加载所有枚举类型 + /// + /// + /// + private static ConcurrentDictionary LoadEnumTypeDict(Assembly assembly) + { + // 取程序集中所有类型 + var typeArray = assembly.GetTypes(); + + // 过滤非枚举类型,转成字典格式并返回 + var dict = typeArray.Where(o => o.IsEnum).ToDictionary(o => o.Name, o => o); + ConcurrentDictionary enumTypeDict = new(dict); + return enumTypeDict; + } + + /// + /// 获取枚举的Description + /// + /// + /// + public static string GetDescription(this System.Enum value) + { + return value.GetType().GetMember(value.ToString()).FirstOrDefault()?.GetCustomAttribute() + ?.Description; + } + + /// + /// 获取枚举的Description + /// + /// + /// + public static string GetDescription(this object value) + { + return value.GetType().GetMember(value.ToString() ?? string.Empty).FirstOrDefault() + ?.GetCustomAttribute()?.Description; + } + + /// + /// 将枚举转成枚举信息集合 + /// + /// + /// + public static List EnumToList(this Type type) + { + if (!type.IsEnum) + throw new ArgumentException("Type '" + type.Name + "' is not an enum."); + var arr = System.Enum.GetNames(type); + return arr.Select(sl => + { + var item = System.Enum.Parse(type, sl); + return new EnumEntity + { + Name = item.ToString(), + Describe = item.GetDescription() ?? item.ToString(), + Value = item.GetHashCode() + }; + }).ToList(); + } + + /// + /// 枚举ToList + /// + /// + /// + /// + public static List EnumToList(this Type type) + { + if (!type.IsEnum) + throw new ArgumentException("Type '" + type.Name + "' is not an enum."); + var arr = System.Enum.GetNames(type); + return arr.Select(name => (T)System.Enum.Parse(type, name)).ToList(); + } +} + +/// +/// 枚举实体 +/// +public class EnumEntity +{ + /// + /// 枚举的描述 + /// + public string Describe { set; get; } + + /// + /// 枚举名称 + /// + public string Name { set; get; } + + /// + /// 枚举对象的值 + /// + public int Value { set; get; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/ListExtensions.cs b/Admin.NET/Admin.NET.Core/Extension/ListExtensions.cs new file mode 100644 index 00000000..77d8c903 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/ListExtensions.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class ListExtensions +{ + public static async Task ForEachAsync(this List list, Func func) + { + foreach (var value in list) + { + await func(value); + } + } + + public static async Task ForEachAsync(this IEnumerable source, Func action) + { + foreach (var value in source) + { + await action(value); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs b/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs new file mode 100644 index 00000000..8f6c106c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs @@ -0,0 +1,449 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 对象拓展 +/// +[SuppressSniffer] +public static partial class ObjectExtension +{ + /// + /// 判断类型是否实现某个泛型 + /// + /// 类型 + /// 泛型类型 + /// bool + public static bool HasImplementedRawGeneric(this Type type, Type generic) + { + // 检查接口类型 + var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); + if (isTheRawGenericType) return true; + + // 检查类型 + while (type != null && type != typeof(object)) + { + isTheRawGenericType = IsTheRawGenericType(type); + if (isTheRawGenericType) return true; + type = type.BaseType; + } + + return false; + + // 判断逻辑 + bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); + } + + /// + /// 将字典转化为QueryString格式 + /// + /// + /// + /// + public static string ToQueryString(this Dictionary dict, bool urlEncode = true) + { + return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}")); + } + + /// + /// 将字符串URL编码 + /// + /// + /// + public static string UrlEncode(this string str) + { + return string.IsNullOrEmpty(str) ? "" : System.Uri.EscapeDataString(str); + } + + /// + /// 对象序列化成Json字符串 + /// + /// + /// + public static string ToJson(this object obj) + { + return JSON.GetJsonSerializer().Serialize(obj); + } + + /// + /// Json字符串反序列化成对象 + /// + /// + /// + /// + public static T ToObject(this string json) + { + return JSON.GetJsonSerializer().Deserialize(json); + } + + /// + /// 将object转换为long,若失败则返回0 + /// + /// + /// + public static long ParseToLong(this object obj) + { + try + { + return long.Parse(obj.ToString()); + } + catch + { + return 0L; + } + } + + /// + /// 将object转换为long,若失败则返回指定值 + /// + /// + /// + /// + public static long ParseToLong(this string str, long defaultValue) + { + try + { + return long.Parse(str); + } + catch + { + return defaultValue; + } + } + + /// + /// 将object转换为double,若失败则返回0 + /// + /// + /// + public static double ParseToDouble(this object obj) + { + try + { + return double.Parse(obj.ToString()); + } + catch + { + return 0; + } + } + + /// + /// 将object转换为double,若失败则返回指定值 + /// + /// + /// + /// + public static double ParseToDouble(this object str, double defaultValue) + { + try + { + return double.Parse(str.ToString()); + } + catch + { + return defaultValue; + } + } + + /// + /// 将string转换为DateTime,若失败则返回日期最小值 + /// + /// + /// + public static DateTime ParseToDateTime(this string str) + { + try + { + if (string.IsNullOrWhiteSpace(str)) + { + return DateTime.MinValue; + } + if (str.Contains('-') || str.Contains('/')) + { + return DateTime.Parse(str); + } + else + { + int length = str.Length; + switch (length) + { + case 4: + return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture); + + case 6: + return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture); + + case 8: + return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture); + + case 10: + return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture); + + case 12: + return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture); + + case 14: + return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture); + + default: + return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture); + } + } + } + catch + { + return DateTime.MinValue; + } + } + + /// + /// 将string转换为DateTime,若失败则返回默认值 + /// + /// + /// + /// + public static DateTime ParseToDateTime(this string str, DateTime? defaultValue) + { + try + { + if (string.IsNullOrWhiteSpace(str)) + { + return defaultValue.GetValueOrDefault(); + } + if (str.Contains('-') || str.Contains('/')) + { + return DateTime.Parse(str); + } + else + { + int length = str.Length; + switch (length) + { + case 4: + return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture); + + case 6: + return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture); + + case 8: + return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture); + + case 10: + return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture); + + case 12: + return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture); + + case 14: + return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture); + + default: + return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture); + } + } + } + catch + { + return defaultValue.GetValueOrDefault(); + } + } + + /// + /// 将 string 时间日期格式转换成字符串 如 {yyyy} => 2024 + /// + /// + /// + public static string ParseToDateTimeForRep(this string str) + { + if (string.IsNullOrWhiteSpace(str)) + str = $"{DateTime.Now.Year}/{DateTime.Now.Month}/{DateTime.Now.Day}"; + + var date = DateTime.Now; + var reg = new Regex(@"(\{.+?})"); + var match = reg.Matches(str); + match.ToList().ForEach(u => + { + var temp = date.ToString(u.ToString().Substring(1, u.Length - 2)); + str = str.Replace(u.ToString(), temp); + }); + return str; + } + + /// + /// 是否有值 + /// + /// + /// + public static bool IsNullOrEmpty(this object obj) + { + return obj == null || string.IsNullOrEmpty(obj.ToString()); + } + + /// + /// 字符串掩码 + /// + /// 字符串 + /// 掩码符 + /// + public static string Mask(this string str, char mask = '*') + { + if (string.IsNullOrWhiteSpace(str?.Trim())) + return str; + + str = str.Trim(); + var masks = mask.ToString().PadLeft(4, mask); + return str.Length switch + { + >= 11 => Regex.Replace(str, "(.{3}).*(.{4})", $"$1{masks}$2"), + 10 => Regex.Replace(str, "(.{3}).*(.{3})", $"$1{masks}$2"), + 9 => Regex.Replace(str, "(.{2}).*(.{3})", $"$1{masks}$2"), + 8 => Regex.Replace(str, "(.{2}).*(.{2})", $"$1{masks}$2"), + 7 => Regex.Replace(str, "(.{1}).*(.{2})", $"$1{masks}$2"), + 6 => Regex.Replace(str, "(.{1}).*(.{1})", $"$1{masks}$2"), + _ => Regex.Replace(str, "(.{1}).*", $"$1{masks}") + }; + } + + /// + /// 身份证号掩码 + /// + /// 身份证号 + /// 掩码符 + /// + public static string MaskIdCard(this string idCard, char mask = '*') + { + if (!idCard.TryValidate(ValidationTypes.IDCard).IsValid) return idCard; + + var masks = mask.ToString().PadLeft(8, mask); + return Regex.Replace(idCard, @"^(.{6})(.*)(.{4})$", $"$1{masks}$3"); + } + + /// + /// 邮箱掩码 + /// + /// 邮箱 + /// 掩码符 + /// + public static string MaskEmail(this string email, char mask = '*') + { + if (!email.TryValidate(ValidationTypes.EmailAddress).IsValid) return email; + + var pos = email.IndexOf("@"); + return Mask(email[..pos], mask) + email[pos..]; + } + + /// + /// 将字符串转为值类型,若没有得到或者错误返回为空 + /// + /// 指定值类型 + /// 传入字符串 + /// 可空值 + public static T? ParseTo(this string str) where T : struct + { + try + { + if (!string.IsNullOrWhiteSpace(str)) + { + MethodInfo method = typeof(T).GetMethod("Parse", new Type[] { typeof(string) }); + if (method != null) + { + T result = (T)method.Invoke(null, new string[] { str }); + return result; + } + } + } + catch + { + } + return null; + } + + /// + /// 将字符串转为值类型,若没有得到或者错误返回为空 + /// + /// 传入字符串 + /// 目标类型 + /// 可空值 + public static object ParseTo(this string str, Type type) + { + try + { + if (type.Name == "String") + return str; + + if (!string.IsNullOrWhiteSpace(str)) + { + var _type = type; + if (type.Name.StartsWith("Nullable")) + _type = type.GetGenericArguments()[0]; + + MethodInfo method = _type.GetMethod("Parse", new Type[] { typeof(string) }); + if (method != null) + return method.Invoke(null, new string[] { str }); + } + } + catch + { + } + return null; + } + + /// + /// 将一个对象属性值赋给另一个指定对象属性, 只复制相同属性的 + /// + /// 原数据对象 + /// 目标数据对象 + /// 属性集,键为原属性,值为目标属性 + /// 属性集,目标不修改的属性 + public static void CopyTo(object src, object target, Dictionary changeProperties = null, string[] unChangeProperties = null) + { + if (src == null || target == null) + throw new ArgumentException("src == null || target == null "); + + var SourceType = src.GetType(); + var TargetType = target.GetType(); + + if (changeProperties == null || changeProperties.Count == 0) + { + var fields = TargetType.GetProperties(); + changeProperties = fields.Select(m => m.Name).ToDictionary(m => m); + } + + if (unChangeProperties == null || unChangeProperties.Length == 0) + { + foreach (var item in changeProperties) + { + var srcProperty = SourceType.GetProperty(item.Key); + if (srcProperty != null) + { + var sourceVal = srcProperty.GetValue(src, null); + + var tarProperty = TargetType.GetProperty(item.Value); + tarProperty?.SetValue(target, sourceVal, null); + } + } + } + else + { + foreach (var item in changeProperties) + { + if (!unChangeProperties.Any(m => m == item.Value)) + { + var srcProperty = SourceType.GetProperty(item.Key); + if (srcProperty != null) + { + var sourceVal = srcProperty.GetValue(src, null); + + var tarProperty = TargetType.GetProperty(item.Value); + tarProperty?.SetValue(target, sourceVal, null); + } + } + } + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/RepositoryExtension.cs b/Admin.NET/Admin.NET.Core/Extension/RepositoryExtension.cs new file mode 100644 index 00000000..c0b518ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/RepositoryExtension.cs @@ -0,0 +1,407 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using MapsterMapper; + +namespace Admin.NET.Core; + +public static class RepositoryExtension +{ + /// + /// 实体假删除 _rep.FakeDelete(entity) + /// + /// + /// + /// + /// + public static int FakeDelete(this ISugarRepository repository, T entity) where T : EntityBase, new() + { + return repository.Context.FakeDelete(entity); + } + + /// + /// 实体假删除 db.FakeDelete(entity) + /// + /// + /// + /// + /// + public static int FakeDelete(this ISqlSugarClient db, T entity) where T : EntityBase, new() + { + return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; }) + .IgnoreColumns(ignoreAllNullColumns: true) + .EnableDiffLogEvent() // 记录差异日志 + .UpdateColumns(x => new { x.IsDelete, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId + .ExecuteCommand(); + } + + /// + /// 实体集合批量假删除 _rep.FakeDelete(entity) + /// + /// + /// + /// + /// + public static int FakeDelete(this ISugarRepository repository, List entity) where T : EntityBase, new() + { + return repository.Context.FakeDelete(entity); + } + + /// + /// 实体集合批量假删除 db.FakeDelete(entity) + /// + /// + /// + /// + /// + public static int FakeDelete(this ISqlSugarClient db, List entity) where T : EntityBase, new() + { + return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; }) + .IgnoreColumns(ignoreAllNullColumns: true) + .EnableDiffLogEvent() // 记录差异日志 + .UpdateColumns(x => new { x.IsDelete, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId + .ExecuteCommand(); + } + + /// + /// 实体假删除异步 _rep.FakeDeleteAsync(entity) + /// + /// + /// + /// + /// + public static Task FakeDeleteAsync(this ISugarRepository repository, T entity) where T : EntityBase, new() + { + return repository.Context.FakeDeleteAsync(entity); + } + + /// + /// 实体假删除 db.FakeDelete(entity) + /// + /// + /// + /// + /// + public static Task FakeDeleteAsync(this ISqlSugarClient db, T entity) where T : EntityBase, new() + { + return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; }) + .IgnoreColumns(ignoreAllNullColumns: true) + .EnableDiffLogEvent() // 记录差异日志 + .UpdateColumns(x => new { x.IsDelete, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId + .ExecuteCommandAsync(); + } + + /// + /// 实体集合批量假删除异步 _rep.FakeDeleteAsync(entity) + /// + /// + /// + /// + /// + public static Task FakeDeleteAsync(this ISugarRepository repository, List entity) where T : EntityBase, new() + { + return repository.Context.FakeDeleteAsync(entity); + } + + /// + /// 实体集合批量假删除 db.FakeDelete(entity) + /// + /// + /// + /// + /// + public static Task FakeDeleteAsync(this ISqlSugarClient db, List entity) where T : EntityBase, new() + { + return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; }) + .IgnoreColumns(ignoreAllNullColumns: true) + .EnableDiffLogEvent() // 记录差异日志 + .UpdateColumns(x => new { x.IsDelete, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId + .ExecuteCommandAsync(); + } + + /// + /// 排序方式(默认降序) + /// + /// + /// + /// + /// 默认排序字段 + /// 是否降序 + /// + public static ISugarQueryable OrderBuilder(this ISugarQueryable queryable, BasePageInput pageInput, string prefix = "", string defaultSortField = "Id", bool descSort = true) + { + // 约定默认每张表都有Id排序 + var orderStr = string.IsNullOrWhiteSpace(defaultSortField) ? "" : $"{prefix}{defaultSortField}" + (descSort ? " Desc" : " Asc"); + + TypeAdapterConfig typeAdapterConfig = new(); + typeAdapterConfig.ForType().IgnoreNullValues(true); + Mapper mapper = new(typeAdapterConfig); // 务必将mapper设为单实例 + var nowPagerInput = mapper.Map(pageInput); + // 排序是否可用-排序字段和排序顺序都为非空才启用排序 + if (!string.IsNullOrEmpty(nowPagerInput.Field) && !string.IsNullOrEmpty(nowPagerInput.Order)) + { + var col = queryable.Context.EntityMaintenance.GetEntityInfo().Columns.FirstOrDefault(u => u.PropertyName.Equals(nowPagerInput.Field, StringComparison.CurrentCultureIgnoreCase)); + orderStr = col != null + ? $"{prefix}{col.DbColumnName} {(nowPagerInput.Order == nowPagerInput.DescStr ? "Desc" : "Asc")}" + : $"{prefix}{nowPagerInput.Field} {(nowPagerInput.Order == nowPagerInput.DescStr ? "Desc" : "Asc")}"; + } + return queryable.OrderByIF(!string.IsNullOrWhiteSpace(orderStr), orderStr); + } + + /// + /// 更新实体并记录差异日志 _rep.UpdateWithDiffLog(entity) + /// + /// + /// + /// + /// + /// + public static int UpdateWithDiffLog(this ISugarRepository repository, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new() + { + return repository.Context.UpdateWithDiffLog(entity, ignoreAllNullColumns); + } + + /// + /// 更新实体并记录差异日志 _rep.UpdateWithDiffLog(entity) + /// + /// + /// + /// + /// + /// + public static int UpdateWithDiffLog(this ISqlSugarClient db, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new() + { + return db.Updateable(entity).AS() + .IgnoreColumns(ignoreAllNullColumns: ignoreAllNullColumns) + .EnableDiffLogEvent() + .ExecuteCommand(); + } + + /// + /// 更新实体并记录差异日志 _rep.UpdateWithDiffLogAsync(entity) + /// + /// + /// + /// + /// + /// + public static Task UpdateWithDiffLogAsync(this ISugarRepository repository, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new() + { + return repository.Context.UpdateWithDiffLogAsync(entity, ignoreAllNullColumns); + } + + /// + /// 更新实体并记录差异日志 _rep.UpdateWithDiffLogAsync(entity) + /// + /// + /// + /// + /// + /// + public static Task UpdateWithDiffLogAsync(this ISqlSugarClient db, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new() + { + return db.Updateable(entity) + .IgnoreColumns(ignoreAllNullColumns: ignoreAllNullColumns) + .EnableDiffLogEvent() + .ExecuteCommandAsync(); + } + + /// + /// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity) + /// + /// + /// + /// + /// + public static int InsertWithDiffLog(this ISugarRepository repository, T entity) where T : EntityBase, new() + { + return repository.Context.InsertWithDiffLog(entity); + } + + /// + /// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity) + /// + /// + /// + /// + /// + public static int InsertWithDiffLog(this ISqlSugarClient db, T entity) where T : EntityBase, new() + { + return db.Insertable(entity).AS().EnableDiffLogEvent().ExecuteCommand(); + } + + /// + /// 新增实体并记录差异日志 _rep.InsertWithDiffLogAsync(entity) + /// + /// + /// + /// + /// + public static Task InsertWithDiffLogAsync(this ISugarRepository repository, T entity) where T : EntityBase, new() + { + return repository.Context.InsertWithDiffLogAsync(entity); + } + + /// + /// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity) + /// + /// + /// + /// + /// + public static Task InsertWithDiffLogAsync(this ISqlSugarClient db, T entity) where T : EntityBase, new() + { + return db.Insertable(entity).AS().EnableDiffLogEvent().ExecuteCommandAsync(); + } + + /// + /// 多库查询 + /// + /// + /// + public static ISugarQueryable AS(this ISugarQueryable queryable) + { + var info = GetTableInfo(); + return queryable.AS($"{info.Item1}.{info.Item2}"); + } + + /// + /// 多库查询 + /// + /// + /// + /// + /// + public static ISugarQueryable AS(this ISugarQueryable queryable) + { + var info = GetTableInfo(); + return queryable.AS($"{info.Item1}.{info.Item2}"); + } + + /// + /// 多库更新 + /// + /// + /// + public static IUpdateable AS(this IUpdateable updateable) where T : EntityBase, new() + { + var info = GetTableInfo(); + return updateable.AS($"{info.Item1}.{info.Item2}"); + } + + /// + /// 多库新增 + /// + /// + /// + public static IInsertable AS(this IInsertable insertable) where T : EntityBase, new() + { + var info = GetTableInfo(); + return insertable.AS($"{info.Item1}.{info.Item2}"); + } + + /// + /// 多库删除 + /// + /// + /// + public static IDeleteable AS(this IDeleteable deleteable) where T : EntityBase, new() + { + var info = GetTableInfo(); + return deleteable.AS($"{info.Item1}.{info.Item2}"); + } + + /// + /// 根据实体类型获取表信息 + /// + /// + /// + private static Tuple GetTableInfo() + { + var entityType = typeof(T); + var attr = entityType.GetCustomAttribute(); + var configId = attr == null ? SqlSugarConst.MainConfigId : attr.configId.ToString(); + var tableName = entityType.GetCustomAttribute().TableName; + return new Tuple(configId, tableName); + } + + /// + /// 禁用过滤器-适用于更新和删除操作(只对当前请求有效,禁止使用异步) + /// + /// + /// 禁止异步 + /// + public static void RunWithoutFilter(this ISugarRepository repository, Action action) + { + repository.Context.QueryFilter.ClearAndBackup(); // 清空并备份过滤器 + action.Invoke(); + repository.Context.QueryFilter.Restore(); // 还原过滤器 + + // 用例 + //_rep.RunWithoutFilter(() => + //{ + // 执行更新或者删除 + // 禁止使用异步函数 + //}); + } + + /// + /// 忽略租户 + /// + /// + /// 是否忽略 默认true + /// + public static ISugarQueryable IgnoreTenant(this ISugarQueryable queryable, bool ignore = true) + { + return ignore ? queryable.ClearFilter() : queryable; + } + + /// + /// 导航+子表过滤 创建一个扩展函数,默认是Class不支持Where + /// + /// + /// + /// + /// + public static List Where(this T thisValue, Func whereExpression) where T : class, new() + { + return new List() { thisValue }; + } + + /// + /// 导航+子表过滤 创建一个扩展函数,默认是Class不支持WhereIF + /// + /// + /// + /// + /// + /// + public static List WhereIF(this T thisValue, bool isWhere, Func whereExpression) where T : class, new() + { + return new List() { thisValue }; + } + + /// + /// 只更新某些列 + /// + /// + /// + /// + /// + public static IUpdateable OnlyUpdateColumn(this IUpdateable updateable) where T : EntityBase, new() where R : class, new() + { + if (updateable.UpdateBuilder.UpdateColumns == null) + updateable.UpdateBuilder.UpdateColumns = new List(); + + foreach (PropertyInfo info in typeof(R).GetProperties()) + { + // 判断是否是相同属性 + if (typeof(T).GetProperty(info.Name) != null) + updateable.UpdateBuilder.UpdateColumns.Add(info.Name); + } + return updateable; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/GlobalUsings.cs b/Admin.NET/Admin.NET.Core/GlobalUsings.cs new file mode 100644 index 00000000..8382f900 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/GlobalUsings.cs @@ -0,0 +1,62 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +global using Admin.NET.Core.Service; +global using Furion; +global using Furion.ClayObject; +global using Furion.ConfigurableOptions; +global using Furion.DatabaseAccessor; +global using Furion.DataEncryption; +global using Furion.DataValidation; +global using Furion.DependencyInjection; +global using Furion.DynamicApiController; +global using Furion.EventBus; +global using Furion.FriendlyException; +global using Furion.JsonSerialization; +global using Furion.Logging; +global using Furion.RemoteRequest.Extensions; +global using Furion.Schedule; +global using Furion.UnifyResult; +global using Furion.ViewEngine; +global using Magicodes.ExporterAndImporter.Core; +global using Magicodes.ExporterAndImporter.Core.Extension; +global using Magicodes.ExporterAndImporter.Excel; +global using Mapster; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.Mvc.Filters; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using NewLife; +global using NewLife.Caching; +global using Newtonsoft.Json.Linq; +global using SKIT.FlurlHttpClient; +global using SKIT.FlurlHttpClient.Wechat.Api; +global using SKIT.FlurlHttpClient.Wechat.Api.Models; +global using SKIT.FlurlHttpClient.Wechat.TenpayV3; +global using SKIT.FlurlHttpClient.Wechat.TenpayV3.Events; +global using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models; +global using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; +global using SqlSugar; +global using System.Collections; +global using System.Collections.Concurrent; +global using System.ComponentModel; +global using System.ComponentModel.DataAnnotations; +global using System.Data; +global using System.Diagnostics; +global using System.Linq.Dynamic.Core; +global using System.Linq.Expressions; +global using System.Reflection; +global using System.Runtime.InteropServices; +global using System.Text; +global using System.Text.RegularExpressions; +global using System.Web; +global using UAParser; +global using Yitter.IdGenerator; \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubInput.cs b/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubInput.cs new file mode 100644 index 00000000..386ad74b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubInput.cs @@ -0,0 +1,12 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public class OnlineUserHubInput +{ + public string ConnectionId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubOutput.cs b/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubOutput.cs new file mode 100644 index 00000000..90c44251 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubOutput.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public class OnlineUserList +{ + public string RealName { get; set; } + + public bool Online { get; set; } + + public List UserList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/IOnlineUserHub.cs b/Admin.NET/Admin.NET.Core/Hub/IOnlineUserHub.cs new file mode 100644 index 00000000..64fa9a29 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Hub/IOnlineUserHub.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public interface IOnlineUserHub +{ + /// + /// 在线用户列表 + /// + /// + /// + Task OnlineUserList(OnlineUserList context); + + /// + /// 强制下线 + /// + /// + /// + Task ForceOffline(object context); + + /// + /// 发布站内消息 + /// + /// + /// + Task PublicNotice(SysNotice context); + + /// + /// 接收消息 + /// + /// + /// + Task ReceiveMessage(object context); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs b/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs new file mode 100644 index 00000000..7973e694 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs @@ -0,0 +1,179 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion.InstantMessaging; +using Microsoft.AspNetCore.SignalR; + +namespace Admin.NET.Core; + +/// +/// 在线用户集线器 +/// +[MapHub("/hubs/onlineUser")] +public class OnlineUserHub : Hub +{ + private const string GROUP_ONLINE = "GROUP_ONLINE_"; // 租户分组前缀 + + private readonly SqlSugarRepository _sysOnlineUerRep; + private readonly SysMessageService _sysMessageService; + private readonly IHubContext _onlineUserHubContext; + private readonly SysCacheService _sysCacheService; + private readonly SysConfigService _sysConfigService; + + public OnlineUserHub(SqlSugarRepository sysOnlineUerRep, + SysMessageService sysMessageService, + IHubContext onlineUserHubContext, + SysCacheService sysCacheService, + SysConfigService sysConfigService) + { + _sysOnlineUerRep = sysOnlineUerRep; + _sysMessageService = sysMessageService; + _onlineUserHubContext = onlineUserHubContext; + _sysCacheService = sysCacheService; + _sysConfigService = sysConfigService; + } + + /// + /// 连接 + /// + /// + public override async Task OnConnectedAsync() + { + var httpContext = Context.GetHttpContext(); + var token = httpContext.Request.Query["access_token"]; + var claims = JWTEncryption.ReadJwtToken(token)?.Claims; + var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]); + + var userId = claims?.FirstOrDefault(u => u.Type == ClaimConst.UserId)?.Value; + var tenantId = claims?.FirstOrDefault(u => u.Type == ClaimConst.TenantId)?.Value; + var user = new SysOnlineUser + { + ConnectionId = Context.ConnectionId, + UserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), + UserName = claims?.FirstOrDefault(u => u.Type == ClaimConst.Account)?.Value, + RealName = claims?.FirstOrDefault(u => u.Type == ClaimConst.RealName)?.Value, + Time = DateTime.Now, + Ip = httpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(), + Browser = client.UA.Family + client.UA.Major, + Os = client.OS.Family + client.OS.Major, + TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : Convert.ToInt64(tenantId), + }; + await _sysOnlineUerRep.InsertAsync(user); + + // 是否开启单用户登录 + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId, user); + } + else + { + var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim(); + _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId + device, user); + } + + // 以租户Id进行分组 + var groupName = $"{GROUP_ONLINE}{user.TenantId}"; + await _onlineUserHubContext.Groups.AddToGroupAsync(Context.ConnectionId, groupName); + + var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true) + .Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync(); + await _onlineUserHubContext.Clients.Groups(groupName).OnlineUserList(new OnlineUserList + { + RealName = user.RealName, + Online = true, + UserList = userList + }); + } + + /// + /// 断开 + /// + /// + /// + public override async Task OnDisconnectedAsync(Exception exception) + { + if (string.IsNullOrEmpty(Context.ConnectionId)) return; + + var httpContext = Context.GetHttpContext(); + var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]); + + var user = await _sysOnlineUerRep.AsQueryable().Filter("", true).FirstAsync(u => u.ConnectionId == Context.ConnectionId); + if (user == null) return; + + await _sysOnlineUerRep.DeleteAsync(u => u.Id == user.Id); + + // 是否开启单用户登录 + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId); + } + else + { + var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim(); + _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId + device); + } + + // 通知当前组用户变动 + var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true) + .Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync(); + await _onlineUserHubContext.Clients.Groups($"{GROUP_ONLINE}{user.TenantId}").OnlineUserList(new OnlineUserList + { + RealName = user.RealName, + Online = false, + UserList = userList + }); + } + + /// + /// 强制下线 + /// + /// + /// + public async Task ForceOffline(OnlineUserHubInput input) + { + await _onlineUserHubContext.Clients.Client(input.ConnectionId).ForceOffline("强制下线"); + } + + /// + /// 发送信息给某个人 + /// + /// + /// + public async Task ClientsSendMessage(MessageInput message) + { + await _sysMessageService.SendUser(message); + } + + /// + /// 发送信息给所有人 + /// + /// + /// + public async Task ClientsSendMessagetoAll(MessageInput message) + { + await _sysMessageService.SendAllUser(message); + } + + /// + /// 发送消息给某些人(除了本人) + /// + /// + /// + public async Task ClientsSendMessagetoOther(MessageInput message) + { + await _sysMessageService.SendOtherUser(message); + } + + /// + /// 发送消息给某些人 + /// + /// + /// + public async Task ClientsSendMessagetoUsers(MessageInput message) + { + await _sysMessageService.SendUsers(message); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/UserIdProvider.cs b/Admin.NET/Admin.NET.Core/Hub/UserIdProvider.cs new file mode 100644 index 00000000..fa6dec89 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Hub/UserIdProvider.cs @@ -0,0 +1,17 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.SignalR; + +namespace Admin.NET.Core; + +public interface UserIdProvider : IUserIdProvider +{ + public new string GetUserId(HubConnectionContext connection) + { + return connection.User?.Claims?.FirstOrDefault(u => u.Type == ClaimConst.UserId)?.Value; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/DynamicJobCompiler.cs b/Admin.NET/Admin.NET.Core/Job/DynamicJobCompiler.cs new file mode 100644 index 00000000..67eb8fa2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Job/DynamicJobCompiler.cs @@ -0,0 +1,25 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 动态作业编译 +/// +public class DynamicJobCompiler : ISingleton +{ + /// + /// 编译代码并返回其中实现 IJob 的类型 + /// + /// 动态编译的作业代码 + /// + public Type BuildJob(string script) + { + var jobAssembly = Schedular.CompileCSharpClassCode(script); + var jobType = jobAssembly.GetTypes().FirstOrDefault(u => typeof(IJob).IsAssignableFrom(u)); + return jobType; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs new file mode 100644 index 00000000..c113b1bb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs @@ -0,0 +1,148 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 枚举转字典 +/// +[JobDetail("job_EnumToDictJob", Description = "枚举转字典", GroupName = "default", Concurrent = false)] +[PeriodSeconds(1, TriggerId = "trigger_EnumToDictJob", Description = "枚举转字典", MaxNumberOfRuns = 1, RunOnStart = true)] +public class EnumToDictJob : IJob +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly IJsonSerializerProvider _jsonSerializer; + + public EnumToDictJob(IServiceScopeFactory scopeFactory, IJsonSerializerProvider jsonSerializer) + { + _scopeFactory = scopeFactory; + _jsonSerializer = jsonSerializer; + } + + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + using var serviceScope = _scopeFactory.CreateScope(); + var sysEnumService = serviceScope.ServiceProvider.GetRequiredService(); + var db = serviceScope.ServiceProvider.GetRequiredService().CopyNew(); + + var enumTypeList = sysEnumService.GetEnumTypeList(); + var enumCodeList = enumTypeList.Select(u => u.TypeName); + var sysDictTypeCodeList = await db.Queryable().Where(u => enumCodeList.Contains(u.Code)).Select(u => u.Code).ToListAsync(stoppingToken); + + // 更新的枚举转换字典 + var uEnumType = enumTypeList.Where(u => sysDictTypeCodeList.Contains(u.TypeName)).ToList(); + var waitUpdateSysDictType = await db.Queryable().Where(u => uEnumType.Any(a => a.TypeName == u.Code)).ToListAsync(stoppingToken); + var waitUpdateSysDictTypeDict = waitUpdateSysDictType.ToDictionary(u => u.Code, u => u); + var waitUpdateSysDictData = await db.Queryable().Where(u => uEnumType.Any(a => a.TypeName == u.DictType.Code)).ToListAsync(stoppingToken); + var uSysDictType = new List(); + var uSysDictData = new List(); + if (uEnumType.Count > 0) + { + uEnumType.ForEach(e => + { + if (waitUpdateSysDictTypeDict.TryGetValue(e.TypeName, out SysDictType value)) + { + var uDictType = value; + uDictType.Name = e.TypeDescribe; + uDictType.Remark = e.TypeRemark; + var uDictData = waitUpdateSysDictData.Where(u => u.DictTypeId == uDictType.Id).ToList(); + if (uDictData.Count > 0) + { + uDictData.ForEach(dictData => + { + var enumData = e.EnumEntities.Where(u => dictData.Code == u.Name).FirstOrDefault(); + if (enumData != null) + { + dictData.Value = enumData.Value.ToString(); + dictData.Code = enumData.Name; + dictData.OrderNo = enumData.Value + 10; + uSysDictData.Add(dictData); + } + }); + } + if (!uSysDictType.Any(u => u.Id == uDictType.Id)) + uSysDictType.Add(uDictType); + } + }); + try + { + db.Ado.BeginTran(); + + if (uSysDictType.Count > 0) + await db.Updateable(uSysDictType).ExecuteCommandAsync(stoppingToken); + + if (uSysDictData.Count > 0) + await db.Updateable(uSysDictData).ExecuteCommandAsync(stoppingToken); + + db.Ado.CommitTran(); + } + catch (Exception error) + { + db.Ado.RollbackTran(); + Log.Error($"{context.Trigger.Description}更新枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); + throw new Exception($"{context.Trigger.Description}更新枚举转换字典入库错误"); + } + } + + // 新增的枚举转换字典 + var iEnumType = enumTypeList.Where(u => !sysDictTypeCodeList.Contains(u.TypeName)).ToList(); + if (iEnumType.Count > 0) + { + // 新增字典类型 + var iDictType = iEnumType.Select(u => new SysDictType + { + Id = YitIdHelper.NextId(), + Code = u.TypeName, + Name = u.TypeDescribe, + Remark = u.TypeRemark, + Status = StatusEnum.Enable + }).ToList(); + // 新增字典数据 + var dictData = iEnumType.Join(iDictType, t1 => t1.TypeName, t2 => t2.Code, (t1, t2) => new + { + data = t1.EnumEntities.Select(u => new SysDictData + { + Id = YitIdHelper.NextId(), // 性能优化,使用BulkCopyAsync必须手动获取Id + DictTypeId = t2.Id, + Name = u.Describe, + Value = u.Value.ToString(), + Code = u.Name, + Remark = t2.Remark, + OrderNo = u.Value + 10, + TagType = "info" + }).ToList() + }).ToList(); + var iDictData = new List(); + dictData.ForEach(item => + { + iDictData.AddRange(item.data); + }); + try + { + db.Ado.BeginTran(); + + if (iDictType.Count > 0) + await db.Insertable(iDictType).ExecuteCommandAsync(stoppingToken); + + if (iDictData.Count > 0) + await db.Insertable(iDictData).ExecuteCommandAsync(stoppingToken); + + db.Ado.CommitTran(); + } + catch (Exception error) + { + db.Ado.RollbackTran(); + Log.Error($"{context.Trigger.Description}新增枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); + throw new Exception($"{context.Trigger.Description}新增枚举转换字典入库错误"); + } + } + + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"【{DateTime.Now}】系统枚举转换字典"); + Console.ForegroundColor = originColor; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/LogJob.cs b/Admin.NET/Admin.NET.Core/Job/LogJob.cs new file mode 100644 index 00000000..b8afb512 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Job/LogJob.cs @@ -0,0 +1,46 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 清理日志作业任务 +/// +[JobDetail("job_log", Description = "清理操作日志", GroupName = "default", Concurrent = false)] +[Daily(TriggerId = "trigger_log", Description = "清理操作日志")] +public class LogJob : IJob +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + public LogJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) + { + _scopeFactory = scopeFactory; + _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName); + } + + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + using var serviceScope = _scopeFactory.CreateScope(); + + var logVisRep = serviceScope.ServiceProvider.GetRequiredService>(); + var logOpRep = serviceScope.ServiceProvider.GetRequiredService>(); + var logDiffRep = serviceScope.ServiceProvider.GetRequiredService>(); + + var daysAgo = 30; // 删除30天以前 + await logVisRep.CopyNew().AsDeleteable().Where(u => (DateTime)u.CreateTime < DateTime.Now.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除访问日志 + await logOpRep.CopyNew().AsDeleteable().Where(u => (DateTime)u.CreateTime < DateTime.Now.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除操作日志 + await logDiffRep.CopyNew().AsDeleteable().Where(u => (DateTime)u.CreateTime < DateTime.Now.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除差异日志 + + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"【{DateTime.Now}】清理系统日志(30天前)"); + Console.ForegroundColor = originColor; + + // 自定义日志 + _logger.LogInformation($"【{DateTime.Now}】清理系统日志..."); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs b/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs new file mode 100644 index 00000000..e1e929ea --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion.Logging.Extensions; + +namespace Admin.NET.Core; + +/// +/// 清理在线用户作业任务 +/// +[JobDetail("job_onlineUser", Description = "清理在线用户", GroupName = "default", Concurrent = false)] +[PeriodSeconds(1, TriggerId = "trigger_onlineUser", Description = "清理在线用户", MaxNumberOfRuns = 1, RunOnStart = true)] +public class OnlineUserJob : IJob +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + public OnlineUserJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) + { + _scopeFactory = scopeFactory; + _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName); + } + + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + using var serviceScope = _scopeFactory.CreateScope(); + + var rep = serviceScope.ServiceProvider.GetRequiredService>(); + await rep.CopyNew().AsDeleteable().ExecuteCommandAsync(stoppingToken); + + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"【{DateTime.Now}】清空在线用户列表"); + Console.ForegroundColor = originColor; + + // 缓存租户列表 + await serviceScope.ServiceProvider.GetRequiredService().CacheTenant(); + + // 自定义日志 + _logger.LogInformation($"【{DateTime.Now}】服务已重启..."); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/RoleApiJob.cs b/Admin.NET/Admin.NET.Core/Job/RoleApiJob.cs new file mode 100644 index 00000000..b7e3a388 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Job/RoleApiJob.cs @@ -0,0 +1,63 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion.Logging.Extensions; + +namespace Admin.NET.Core; + +/// +/// 初始化管理员角色接口资源作业任务 +/// +[JobDetail("job_roleApi", Description = "初始化管理员角色接口资源", GroupName = "default", Concurrent = false)] +[PeriodSeconds(1, TriggerId = "trigger_roleApi", Description = "初始化管理员角色接口资源", MaxNumberOfRuns = 1, RunOnStart = true)] +public class RoleApiJob : IJob +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + public RoleApiJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) + { + _scopeFactory = scopeFactory; + _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName); + } + + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + using var serviceScope = _scopeFactory.CreateScope(); + + // 获取所有接口列表-排除租户接口 + var apiList = App.GetRequiredService().GetApiList(); + + var index = 0; + var sysRoleApis = new List(); + foreach (var baseApi in apiList) + { + foreach (var api in baseApi.Apis) + { + // 排除租户管理接口 + if (api.ControllerName == "sysTenant") continue; + + // 为系统管理员分配接口 + sysRoleApis.Add(new SysRoleApi { Id = 1400000000101 + index, RoleId = 1300000000101, Route = api.Route }); + index++; + } + } + var db = serviceScope.ServiceProvider.GetRequiredService(); + var storage = db.StorageableByObject(sysRoleApis).ToStorage(); + storage.AsInsertable.ExecuteCommand(); + storage.AsUpdateable.ExecuteCommand(); + + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine($"【{DateTime.Now}】初始化管理员角色接口资源"); + Console.ForegroundColor = originColor; + + // 自定义日志 + _logger.LogInformation($"【{DateTime.Now}】初始化管理员角色接口资源"); + + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs b/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs new file mode 100644 index 00000000..5d40a339 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs @@ -0,0 +1,225 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using IPTools.Core; + +namespace Admin.NET.Core; + +/// +/// 数据库日志写入器 +/// +public class DatabaseLoggingWriter : IDatabaseLoggingWriter, IDisposable +{ + private readonly IServiceScope _serviceScope; + private readonly ISqlSugarClient _db; + private readonly SysConfigService _sysConfigService; // 参数配置服务 + private readonly ILogger _logger; // 日志组件 + + public DatabaseLoggingWriter(IServiceScopeFactory scopeFactory) + { + _serviceScope = scopeFactory.CreateScope(); + //_db = _serviceScope.ServiceProvider.GetRequiredService(); + _sysConfigService = _serviceScope.ServiceProvider.GetRequiredService(); + _logger = _serviceScope.ServiceProvider.GetRequiredService>(); + + // 切换日志独立数据库 + _db = SqlSugarSetup.ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) + ? SqlSugarSetup.ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) + : SqlSugarSetup.ITenant.GetConnectionScope(SqlSugarConst.MainConfigId); + } + + public async Task WriteAsync(LogMessage logMsg, bool flush) + { + var jsonStr = logMsg.Context?.Get("loggingMonitor")?.ToString(); + if (string.IsNullOrWhiteSpace(jsonStr)) + { + await _db.Insertable(new SysLogOp + { + DisplayTitle = "自定义操作日志", + LogDateTime = logMsg.LogDateTime, + EventId = logMsg.EventId.Id, + ThreadId = logMsg.ThreadId, + TraceId = logMsg.TraceId, + Exception = logMsg.Exception == null ? null : JSON.Serialize(logMsg.Exception), + Message = logMsg.Message, + LogLevel = logMsg.LogLevel, + Status = "200", + }).ExecuteCommandAsync(); + return; + } + + var loggingMonitor = JSON.Deserialize(jsonStr); + // 不记录数据校验日志 + if (loggingMonitor.validation != null) return; + + // 获取当前操作者 + string account = "", realName = "", userId = "", tenantId = ""; + if (loggingMonitor.authorizationClaims != null) + { + foreach (var item in loggingMonitor.authorizationClaims) + { + if (item.type == ClaimConst.Account) + account = item.value; + if (item.type == ClaimConst.RealName) + realName = item.value; + if (item.type == ClaimConst.TenantId) + tenantId = item.value; + if (item.type == ClaimConst.UserId) + userId = item.value; + } + } + + string remoteIPv4 = loggingMonitor.remoteIPv4; + (string ipLocation, double? longitude, double? latitude) = GetIpAddress(remoteIPv4); + + var browser = ""; + var os = ""; + if (loggingMonitor.userAgent != null) + { + var client = Parser.GetDefault().Parse(loggingMonitor.userAgent.ToString()); + browser = $"{client.UA.Family} {client.UA.Major}.{client.UA.Minor} / {client.Device.Family}"; + os = $"{client.OS.Family} {client.OS.Major} {client.OS.Minor}"; + } + + // 捕捉异常,否则会由于 unhandled exception 导致程序崩溃 + try + { + // 记录异常日志-发送邮件 + if (logMsg.Exception != null || loggingMonitor.exception != null) + { + await _db.Insertable(new SysLogEx + { + ControllerName = loggingMonitor.controllerName, + ActionName = loggingMonitor.actionTypeName, + DisplayTitle = loggingMonitor.displayTitle, + Status = loggingMonitor.returnInformation?.httpStatusCode, + RemoteIp = remoteIPv4, + Location = ipLocation, + Longitude = longitude, + Latitude = latitude, + Browser = browser, // loggingMonitor.userAgent, + Os = os, // loggingMonitor.osDescription + " " + loggingMonitor.osArchitecture, + Elapsed = loggingMonitor.timeOperationElapsedMilliseconds, + LogDateTime = logMsg.LogDateTime, + Account = account, + RealName = realName, + HttpMethod = loggingMonitor.httpMethod, + RequestUrl = loggingMonitor.requestUrl, + RequestParam = (loggingMonitor.parameters == null || loggingMonitor.parameters.Count == 0) ? null : JSON.Serialize(loggingMonitor.parameters[0].value), + ReturnResult = loggingMonitor.returnInformation == null ? null : JSON.Serialize(loggingMonitor.returnInformation), + EventId = logMsg.EventId.Id, + ThreadId = logMsg.ThreadId, + TraceId = logMsg.TraceId, + Exception = JSON.Serialize(loggingMonitor.exception), + Message = logMsg.Message, + CreateUserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), + TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : long.Parse(tenantId), + LogLevel = logMsg.LogLevel + }).ExecuteCommandAsync(); + + // 将异常日志发送到邮件 + if (await _sysConfigService.GetConfigValue(CommonConst.SysErrorMail)) + { + await App.GetRequiredService().PublishAsync(CommonConst.SendErrorMail, loggingMonitor.exception); + } + + return; + } + + // 记录访问日志-登录退出 + if (loggingMonitor.actionName == "userInfo" || loggingMonitor.actionName == "logout") + { + await _db.Insertable(new SysLogVis + { + ControllerName = loggingMonitor.controllerName, + ActionName = loggingMonitor.actionTypeName, + DisplayTitle = loggingMonitor.displayTitle, + Status = loggingMonitor.returnInformation?.httpStatusCode, + RemoteIp = remoteIPv4, + Location = ipLocation, + Longitude = longitude, + Latitude = latitude, + Browser = browser, // loggingMonitor.userAgent, + Os = os, // loggingMonitor.osDescription + " " + loggingMonitor.osArchitecture, + Elapsed = loggingMonitor.timeOperationElapsedMilliseconds, + LogDateTime = logMsg.LogDateTime, + Account = account, + RealName = realName, + CreateUserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), + TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : long.Parse(tenantId), + LogLevel = logMsg.LogLevel + }).ExecuteCommandAsync(); + return; + } + + // 记录操作日志 + if (!(await _sysConfigService.GetConfigValue(CommonConst.SysOpLog))) return; + await _db.Insertable(new SysLogOp + { + ControllerName = loggingMonitor.controllerName, + ActionName = loggingMonitor.actionTypeName, + DisplayTitle = loggingMonitor.displayTitle, + Status = loggingMonitor.returnInformation?.httpStatusCode, + RemoteIp = remoteIPv4, + Location = ipLocation, + Longitude = longitude, + Latitude = latitude, + Browser = browser, // loggingMonitor.userAgent, + Os = os, // loggingMonitor.osDescription + " " + loggingMonitor.osArchitecture, + Elapsed = loggingMonitor.timeOperationElapsedMilliseconds, + LogDateTime = logMsg.LogDateTime, + Account = account, + RealName = realName, + HttpMethod = loggingMonitor.httpMethod, + RequestUrl = loggingMonitor.requestUrl, + RequestParam = (loggingMonitor.parameters == null || loggingMonitor.parameters.Count == 0) ? null : JSON.Serialize(loggingMonitor.parameters[0].value), + ReturnResult = loggingMonitor.returnInformation == null ? null : JSON.Serialize(loggingMonitor.returnInformation), + EventId = logMsg.EventId.Id, + ThreadId = logMsg.ThreadId, + TraceId = logMsg.TraceId, + Exception = loggingMonitor.exception == null ? null : JSON.Serialize(loggingMonitor.exception), + Message = logMsg.Message, + CreateUserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), + TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : long.Parse(tenantId), + LogLevel = logMsg.LogLevel + }).ExecuteCommandAsync(); + + await Task.Delay(50); // 延迟 0.05 秒写入数据库,有效减少高频写入数据库导致死锁问题 + } + catch (Exception ex) + { + _logger.LogError(ex, "操作日志入库"); + } + } + + /// + /// 解析IP地址 + /// + /// + /// + internal static (string ipLocation, double? longitude, double? latitude) GetIpAddress(string ip) + { + try + { + var ipInfo = IpTool.SearchWithI18N(ip); // 国际化查询,默认中文 中文zh-CN、英文en + var addressList = new List() { ipInfo.Country, ipInfo.Province, ipInfo.City, ipInfo.NetworkOperator }; + return (string.Join(" ", addressList.Where(u => u != "0" && !string.IsNullOrWhiteSpace(u)).ToList()), ipInfo.Longitude, ipInfo.Latitude); // 去掉0及空并用空格连接 + } + catch + { + // 不做处理 + } + return ("未知", 0, 0); + } + + /// + /// 释放服务作用域 + /// + public void Dispose() + { + _serviceScope.Dispose(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs new file mode 100644 index 00000000..731647ff --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs @@ -0,0 +1,101 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Elastic.Clients.Elasticsearch; + +namespace Admin.NET.Core; + +/// +/// ES日志写入器 +/// +public class ElasticSearchLoggingWriter : IDatabaseLoggingWriter, IDisposable +{ + private readonly IServiceScope _serviceScope; + private readonly ElasticsearchClient _esClient; + private readonly SysConfigService _sysConfigService; + + public ElasticSearchLoggingWriter(IServiceScopeFactory scopeFactory) + { + _serviceScope = scopeFactory.CreateScope(); + _esClient = _serviceScope.ServiceProvider.GetRequiredService(); + _sysConfigService = _serviceScope.ServiceProvider.GetRequiredService(); + } + + public async Task WriteAsync(LogMessage logMsg, bool flush) + { + // 是否启用操作日志 + var sysOpLogEnabled = await _sysConfigService.GetConfigValue(CommonConst.SysOpLog); + if (!sysOpLogEnabled) return; + + var jsonStr = logMsg.Context?.Get("loggingMonitor")?.ToString(); + if (string.IsNullOrWhiteSpace(jsonStr)) return; + + var loggingMonitor = JSON.Deserialize(jsonStr); + + // 不记录登录退出日志 + if (loggingMonitor.actionName == "userInfo" || loggingMonitor.actionName == "logout") + return; + + // 获取当前操作者 + string account = "", realName = "", userId = "", tenantId = ""; + if (loggingMonitor.authorizationClaims != null) + { + foreach (var item in loggingMonitor.authorizationClaims) + { + if (item.type == ClaimConst.Account) + account = item.value; + if (item.type == ClaimConst.RealName) + realName = item.value; + if (item.type == ClaimConst.TenantId) + tenantId = item.value; + if (item.type == ClaimConst.UserId) + userId = item.value; + } + } + + string remoteIPv4 = loggingMonitor.remoteIPv4; + (string ipLocation, double? longitude, double? latitude) = DatabaseLoggingWriter.GetIpAddress(remoteIPv4); + + var sysLogOp = new SysLogOp + { + Id = DateTime.Now.Ticks, + ControllerName = loggingMonitor.controllerName, + ActionName = loggingMonitor.actionTypeName, + DisplayTitle = loggingMonitor.displayTitle, + Status = loggingMonitor.returnInformation.httpStatusCode, + RemoteIp = remoteIPv4, + Location = ipLocation, + Longitude = longitude, + Latitude = latitude, + Browser = loggingMonitor.userAgent, + Os = loggingMonitor.osDescription + " " + loggingMonitor.osArchitecture, + Elapsed = loggingMonitor.timeOperationElapsedMilliseconds, + LogDateTime = logMsg.LogDateTime, + Account = account, + RealName = realName, + HttpMethod = loggingMonitor.httpMethod, + RequestUrl = loggingMonitor.requestUrl, + RequestParam = (loggingMonitor.parameters == null || loggingMonitor.parameters.Count == 0) ? null : JSON.Serialize(loggingMonitor.parameters[0].value), + ReturnResult = JSON.Serialize(loggingMonitor.returnInformation), + EventId = logMsg.EventId.Id, + ThreadId = logMsg.ThreadId, + TraceId = logMsg.TraceId, + Exception = (loggingMonitor.exception == null) ? null : JSON.Serialize(loggingMonitor.exception), + Message = logMsg.Message, + CreateUserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), + TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : long.Parse(tenantId) + }; + await _esClient.IndexAsync(sysLogOp); + } + + /// + /// 释放服务作用域 + /// + public void Dispose() + { + _serviceScope.Dispose(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Logging/ElasticSearchSetup.cs b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchSetup.cs new file mode 100644 index 00000000..aa53c17c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchSetup.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Elastic.Clients.Elasticsearch; +using Elastic.Transport; + +namespace Admin.NET.Core; + +/// +/// ES服务注册 +/// +public static class ElasticSearchSetup +{ + public static void AddElasticSearch(this IServiceCollection services) + { + var option = App.GetConfig("Logging:ElasticSearch"); + if (!option.Enabled) return; + + var uris = option.ServerUris.Select(u => new Uri(u)); + // 集群 + var connectionPool = new StaticNodePool(uris); + var connectionSettings = new ElasticsearchClientSettings(connectionPool).DefaultIndex(option.DefaultIndex); + // 单连接 + //var connectionSettings = new ElasticsearchClientSettings(new StaticNodePool(new List { uris.FirstOrDefault() })).DefaultIndex(option.DefaultIndex); + + // 认证类型 + if (option.AuthType == ElasticSearchAuthTypeEnum.Basic) // Basic 认证 + { + connectionSettings.Authentication(new BasicAuthentication(option.User, option.Password)); + } + else if (option.AuthType == ElasticSearchAuthTypeEnum.ApiKey) // ApiKey 认证 + { + connectionSettings.Authentication(new ApiKey(option.ApiKey)); + } + else if (option.AuthType == ElasticSearchAuthTypeEnum.Base64ApiKey) // Base64ApiKey 认证 + { + connectionSettings.Authentication(new Base64ApiKey(option.Base64ApiKey)); + } + else return; + + // ES使用Https时的证书指纹 + if (!string.IsNullOrEmpty(option.Fingerprint)) + { + connectionSettings.CertificateFingerprint(option.Fingerprint); + } + + var client = new ElasticsearchClient(connectionSettings); + services.AddSingleton(client); // 单例注册 + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Logging/LogExceptionHandler.cs b/Admin.NET/Admin.NET.Core/Logging/LogExceptionHandler.cs new file mode 100644 index 00000000..7895563b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Logging/LogExceptionHandler.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +//using Microsoft.AspNetCore.Mvc.Controllers; +//using System.Security.Claims; + +//namespace Admin.NET.Core.Logging; + +///// +///// 全局异常处理 +///// +//public class LogExceptionHandler : IGlobalExceptionHandler, ISingleton +//{ +// private readonly IEventPublisher _eventPublisher; + +// public LogExceptionHandler(IEventPublisher eventPublisher) +// { +// _eventPublisher = eventPublisher; +// } + +// public async Task OnExceptionAsync(ExceptionContext context) +// { +// var actionMethod = (context.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo; +// var displayNameAttribute = actionMethod.IsDefined(typeof(DisplayNameAttribute), true) ? actionMethod.GetCustomAttribute(true) : default; + +// var sysLogEx = new SysLogEx +// { +// Account = App.User?.FindFirstValue(ClaimConst.Account), +// RealName = App.User?.FindFirstValue(ClaimConst.RealName), +// ControllerName = actionMethod.DeclaringType.FullName, +// ActionName = actionMethod.Name, +// DisplayTitle = displayNameAttribute?.DisplayName, +// Exception = $"异常信息:{context.Exception.Message} 异常来源:{context.Exception.Source} 堆栈信息:{context.Exception.StackTrace}", +// Message = "全局异常", +// RequestParam = context.Exception.TargetSite.GetParameters().ToString(), +// HttpMethod = context.HttpContext.Request.Method, +// RequestUrl = context.HttpContext.Request.GetRequestUrlAddress(), +// RemoteIp = context.HttpContext.GetRemoteIpAddressToIPv4(), +// Browser = context.HttpContext.Request.Headers["User-Agent"], +// TraceId = App.GetTraceId(), +// ThreadId = App.GetThreadId(), +// LogDateTime = DateTime.Now, +// LogLevel = LogLevel.Error +// }; + +// await _eventPublisher.PublishAsync(new ChannelEventSource("Add:ExLog", sysLogEx)); + +// await _eventPublisher.PublishAsync("Send:ErrorMail", sysLogEx); +// } +//} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Logging/LoggingSetup.cs b/Admin.NET/Admin.NET.Core/Logging/LoggingSetup.cs new file mode 100644 index 00000000..b0e502df --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Logging/LoggingSetup.cs @@ -0,0 +1,98 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class LoggingSetup +{ + /// + /// 日志注册 + /// + /// + public static void AddLoggingSetup(this IServiceCollection services) + { + // 日志监听 + services.AddMonitorLogging(options => + { + options.IgnorePropertyNames = new[] { "Byte" }; + options.IgnorePropertyTypes = new[] { typeof(byte[]) }; + }); + + // 控制台日志 + var consoleLog = App.GetConfig("Logging:Monitor:ConsoleLog", true); + services.AddConsoleFormatter(options => + { + options.DateFormat = "yyyy-MM-dd HH:mm:ss(zzz) dddd"; + //options.WithTraceId = true; // 显示线程Id + //options.WithStackFrame = true; // 显示程序集 + options.WriteFilter = (logMsg) => + { + return consoleLog; + }; + }); + + // 日志写入文件 + if (App.GetConfig("Logging:File:Enabled", true)) + { + var loggingMonitorSettings = App.GetConfig("Logging:Monitor", true); + Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel => + { + services.AddFileLogging(options => + { + options.WithTraceId = true; // 显示线程Id + options.WithStackFrame = true; // 显示程序集 + options.FileNameRule = fileName => string.Format(fileName, DateTime.Now, logLevel.ToString()); // 每天创建一个文件 + options.WriteFilter = logMsg => logMsg.LogLevel == logLevel; // 日志级别 + options.HandleWriteError = (writeError) => // 写入失败时启用备用文件 + { + writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName)); + }; + if (loggingMonitorSettings.JsonBehavior == JsonBehavior.OnlyJson) + { + options.MessageFormat = LoggerFormatter.Json; + // options.MessageFormat = LoggerFormatter.JsonIndented; + options.MessageFormat = (logMsg) => + { + var jsonString = logMsg.Context.Get("loggingMonitor"); + return jsonString?.ToString(); + }; + } + }); + }); + } + + // 日志写入ElasticSearch + if (App.GetConfig("Logging:ElasticSearch:Enabled", true)) + { + services.AddDatabaseLogging(options => + { + options.WithTraceId = true; // 显示线程Id + options.WithStackFrame = true; // 显示程序集 + options.IgnoreReferenceLoop = false; // 忽略循环检测 + options.MessageFormat = LoggerFormatter.Json; + options.WriteFilter = (logMsg) => + { + return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志 + }; + }); + } + + // 日志写入数据库 + if (App.GetConfig("Logging:Database:Enabled", true)) + { + services.AddDatabaseLogging(options => + { + options.WithTraceId = true; // 显示线程Id + options.WithStackFrame = true; // 显示程序集 + options.IgnoreReferenceLoop = false; // 忽略循环检测 + options.WriteFilter = (logMsg) => + { + return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志 + }; + }); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/APIJSONOptions.cs b/Admin.NET/Admin.NET.Core/Option/APIJSONOptions.cs new file mode 100644 index 00000000..c12625ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/APIJSONOptions.cs @@ -0,0 +1,70 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// APIJSON配置选项 +/// +public sealed class APIJSONOptions : IConfigurableOptions +{ + /// + /// 角色集合 + /// + public List Roles { get; set; } +} + +/// +/// APIJSON角色权限 +/// +public class APIJSON_Role +{ + /// + /// 角色名称 + /// + public string RoleName { get; set; } + + /// + /// 查询 + /// + public APIJSON_RoleItem Select { get; set; } + + /// + /// 增加 + /// + public APIJSON_RoleItem Insert { get; set; } + + /// + /// 更新 + /// + public APIJSON_RoleItem Update { get; set; } + + /// + /// 删除 + /// + public APIJSON_RoleItem Delete { get; set; } +} + +/// +/// APIJSON角色权限内容 +/// +public class APIJSON_RoleItem +{ + /// + /// 表集合 + /// + public string[] Table { get; set; } + + /// + /// 列集合 + /// + public string[] Column { get; set; } + + /// + /// 过滤器 + /// + public string[] Filter { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/CacheOptions.cs b/Admin.NET/Admin.NET.Core/Option/CacheOptions.cs new file mode 100644 index 00000000..b9ee81bc --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/CacheOptions.cs @@ -0,0 +1,142 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 缓存配置选项 +/// +public sealed class CacheOptions : IConfigurableOptions +{ + /// + /// 缓存前缀 + /// + public string Prefix { get; set; } + + /// + /// 缓存类型 + /// + public string CacheType { get; set; } + + /// + /// Redis缓存 + /// + public RedisOption Redis { get; set; } + + public void PostConfigure(CacheOptions options, IConfiguration configuration) + { + options.Prefix = string.IsNullOrWhiteSpace(options.Prefix) ? "" : options.Prefix.Trim(); + } +} + +/// +/// Redis缓存 +/// +public sealed class RedisOption : RedisOptions +{ + /// + /// 最大消息大小 + /// + public int MaxMessageSize { get; set; } +} + +/// +/// 集群配置选项 +/// +public sealed class ClusterOptions : IConfigurableOptions +{ + /// + /// 是否启用 + /// + public bool Enabled { get; set; } + + /// + /// 服务器标识 + /// + public string ServerId { get; set; } + + /// + /// 服务器IP + /// + public string ServerIp { get; set; } + + /// + /// SignalR配置 + /// + public ClusterSignalR SignalR { get; set; } + + /// + /// 数据保护key + /// + public string DataProtecteKey { get; set; } + + /// + /// 是否哨兵模式 + /// + public bool IsSentinel { get; set; } + + /// + /// 哨兵配置 + /// + public StackExchangeSentinelConfig SentinelConfig { get; set; } +} + +/// +/// 集群SignalR配置 +/// +public sealed class ClusterSignalR +{ + /// + /// Redis连接字符串 + /// + public string RedisConfiguration { get; set; } + + /// + /// 缓存前缀 + /// + public string ChannelPrefix { get; set; } +} + +/// +/// 哨兵配置 +/// +public sealed class StackExchangeSentinelConfig +{ + /// + /// master名称 + /// + public string ServiceName { get; set; } + + /// + /// master访问密码 + /// + public string Password { get; set; } + + /// + /// 哨兵访问密码 + /// + public string SentinelPassword { get; set; } + + /// + /// 哨兵端口 + /// + public List EndPoints { get; set; } + + /// + /// 默认库 + /// + public int DefaultDb { get; set; } + + /// + /// 主前缀 + /// + public string MainPrefix { get; set; } + + /// + /// SignalR前缀 + /// + public string SignalRChannelPrefix { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/CodeGenOptions.cs b/Admin.NET/Admin.NET.Core/Option/CodeGenOptions.cs new file mode 100644 index 00000000..021beb79 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/CodeGenOptions.cs @@ -0,0 +1,38 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 代码生成配置选项 +/// +public sealed class CodeGenOptions : IConfigurableOptions +{ + /// + /// 数据库实体程序集名称集合 + /// + public List EntityAssemblyNames { get; set; } + + /// + /// 数据库基础实体名称集合 + /// + public List BaseEntityNames { get; set; } + + /// + /// 基础实体名 + /// + public Dictionary EntityBaseColumn { get; set; } + + /// + /// 前端文件根目录 + /// + public string FrontRootPath { get; set; } + + /// + /// 后端生成到的项目 + /// + public List BackendApplicationNamespaces { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/CryptogramOptions.cs b/Admin.NET/Admin.NET.Core/Option/CryptogramOptions.cs new file mode 100644 index 00000000..44a43ecc --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/CryptogramOptions.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 密码配置选项 +/// +public sealed class CryptogramOptions : IConfigurableOptions +{ + /// + /// 是否开启密码强度验证 + /// + public bool StrongPassword { get; set; } + + /// + /// 密码强度验证正则表达式 + /// + public string PasswordStrengthValidation { get; set; } + + /// + /// 密码强度验证提示 + /// + public string PasswordStrengthValidationMsg { get; set; } + + /// + /// 密码类型 + /// + public string CryptoType { get; set; } + + /// + /// 公钥 + /// + public string PublicKey { get; set; } + + /// + /// 私钥 + /// + public string PrivateKey { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/DbConnectionOptions.cs b/Admin.NET/Admin.NET.Core/Option/DbConnectionOptions.cs new file mode 100644 index 00000000..e1c86190 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/DbConnectionOptions.cs @@ -0,0 +1,106 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 数据库配置选项 +/// +public sealed class DbConnectionOptions : IConfigurableOptions +{ + /// + /// 启用控制台打印SQL + /// + public bool EnableConsoleSql { get; set; } + + /// + /// 数据库集合 + /// + public List ConnectionConfigs { get; set; } + + public void PostConfigure(DbConnectionOptions options, IConfiguration configuration) + { + foreach (var dbConfig in options.ConnectionConfigs) + { + if (dbConfig.ConfigId == null || string.IsNullOrWhiteSpace(dbConfig.ConfigId.ToString())) + dbConfig.ConfigId = SqlSugarConst.MainConfigId; + } + } +} + +/// +/// 数据库连接配置 +/// +public sealed class DbConnectionConfig : ConnectionConfig +{ + /// + /// 数据库配置 + /// + public DbSettings DbSettings { get; set; } + + /// + /// 表配置 + /// + public TableSettings TableSettings { get; set; } + + /// + /// 种子配置 + /// + public SeedSettings SeedSettings { get; set; } +} + +/// +/// 数据库配置 +/// +public sealed class DbSettings +{ + /// + /// 启用库表初始化 + /// + public bool EnableInitDb { get; set; } + + /// + /// 启用库表差异日志 + /// + public bool EnableDiffLog { get; set; } + + /// + /// 启用驼峰转下划线 + /// + public bool EnableUnderLine { get; set; } +} + +/// +/// 表配置 +/// +public sealed class TableSettings +{ + /// + /// 启用表初始化 + /// + public bool EnableInitTable { get; set; } + + /// + /// 启用表增量更新 + /// + public bool EnableIncreTable { get; set; } +} + +/// +/// 种子配置 +/// +public sealed class SeedSettings +{ + /// + /// 启用种子初始化 + /// + public bool EnableInitSeed { get; set; } + + /// + /// 启用种子增量更新 + /// + public bool EnableIncreSeed { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/ElasticSearchOptions.cs b/Admin.NET/Admin.NET.Core/Option/ElasticSearchOptions.cs new file mode 100644 index 00000000..1e6f73af --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/ElasticSearchOptions.cs @@ -0,0 +1,64 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// ES配置选项 +/// +public class ElasticSearchOptions +{ + /// + /// 是否启用 + /// + public bool Enabled { get; set; } = false; + + /// + /// ES认证类型,可选 Basic、ApiKey、Base64ApiKey + /// + public ElasticSearchAuthTypeEnum AuthType { get; set; } + + /// + /// Basic认证的用户名 + /// + public string User { get; set; } + + /// + /// Basic认证的密码 + /// + public string Password { get; set; } + + /// + /// ApiKey认证的ApiId + /// + public string ApiId { get; set; } + + /// + /// ApiKey认证的ApiKey + /// + public string ApiKey { get; set; } + + /// + /// Base64ApiKey认证时加密的加密字符串 + /// + public string Base64ApiKey { get; set; } + + /// + /// ES使用Https时的证书指纹,使用证书请自行实现 + /// https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/connecting.html + /// + public string Fingerprint { get; set; } + + /// + /// 地址 + /// + public List ServerUris { get; set; } = new List(); + + /// + /// 索引 + /// + public string DefaultIndex { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/EmailOptions.cs b/Admin.NET/Admin.NET.Core/Option/EmailOptions.cs new file mode 100644 index 00000000..10933b40 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/EmailOptions.cs @@ -0,0 +1,58 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 邮件配置选项 +/// +public sealed class EmailOptions : IConfigurableOptions +{ + /// + /// 主机 + /// + public string Host { get; set; } + + /// + /// 端口 + /// + public int Port { get; set; } + + /// + /// 默认发件者邮箱 + /// + public string DefaultFromEmail { get; set; } + + /// + /// 默认接收人邮箱 + /// + public string DefaultToEmail { get; set; } + + /// + /// 启用SSL + /// + public bool EnableSsl { get; set; } + + ///// + ///// 是否使用默认凭据 + ///// + //public bool UseDefaultCredentials { get; set; } + + /// + /// 邮箱账号 + /// + public string UserName { get; set; } + + /// + /// 邮箱密码 + /// + public string Password { get; set; } + + /// + /// 默认邮件标题 + /// + public string DefaultFromName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/EnumOptions.cs b/Admin.NET/Admin.NET.Core/Option/EnumOptions.cs new file mode 100644 index 00000000..2da89929 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/EnumOptions.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 枚举配置选项 +/// +public sealed class EnumOptions : IConfigurableOptions +{ + /// + /// 枚举实体程序集名称集合 + /// + public List EntityAssemblyNames { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/EventBusOptions.cs b/Admin.NET/Admin.NET.Core/Option/EventBusOptions.cs new file mode 100644 index 00000000..45e7261c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/EventBusOptions.cs @@ -0,0 +1,44 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 事件总线配置选项 +/// +public sealed class EventBusOptions : IConfigurableOptions +{ + /// + /// RabbitMQ + /// + public RabbitMQSettings RabbitMQ { get; set; } +} + +/// +/// RabbitMQ +/// +public sealed class RabbitMQSettings +{ + /// + /// 账号 + /// + public string UserName { get; set; } + + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 主机 + /// + public string HostName { get; set; } + + /// + /// 端口 + /// + public int Port { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/OAuthOptions.cs b/Admin.NET/Admin.NET.Core/Option/OAuthOptions.cs new file mode 100644 index 00000000..4ea822b4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/OAuthOptions.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 第三方登录授权配置选项 +/// +public sealed class OAuthOptions : IConfigurableOptions +{ + /// + /// Weixin配置 + /// + public Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions Weixin { get; set; } + + /// + /// Gitee配置 + /// + public Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions Gitee { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/PayCallBackOptions.cs b/Admin.NET/Admin.NET.Core/Option/PayCallBackOptions.cs new file mode 100644 index 00000000..b110fd08 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/PayCallBackOptions.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 支付回调配置选项 +/// +public sealed class PayCallBackOptions : IConfigurableOptions +{ + /// + /// 微信支付回调 + /// + public string WechatPayUrl { get; set; } + + /// + /// 微信退款回调 + /// + public string WechatRefundUrl { get; set; } + + /// + /// 支付宝支付回调 + /// + public string AlipayUrl { get; set; } + + /// + /// 支付宝退款回调 + /// + public string AlipayRefundUrl { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/RateLimitOptions.cs b/Admin.NET/Admin.NET.Core/Option/RateLimitOptions.cs new file mode 100644 index 00000000..057166a7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/RateLimitOptions.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using AspNetCoreRateLimit; + +namespace Admin.NET.Core; + +/// +/// IP限流配置选项 +/// +public sealed class IpRateLimitingOptions : IpRateLimitOptions +{ +} + +/// +/// IP限流策略配置选项 +/// +public sealed class IpRateLimitPoliciesOptions : IpRateLimitPolicies, IConfigurableOptions +{ +} + +/// +/// 客户端限流配置选项 +/// +public sealed class ClientRateLimitingOptions : ClientRateLimitOptions, IConfigurableOptions +{ +} + +/// +/// 客户端限流策略配置选项 +/// +public sealed class ClientRateLimitPoliciesOptions : ClientRateLimitPolicies, IConfigurableOptions +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/SMSOptions.cs b/Admin.NET/Admin.NET.Core/Option/SMSOptions.cs new file mode 100644 index 00000000..757755f7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/SMSOptions.cs @@ -0,0 +1,51 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 短信配置选项 +/// +public sealed class SMSOptions : IConfigurableOptions +{ + /// + /// Aliyun + /// + public SMSSettings Aliyun { get; set; } + + /// + /// Tencentyun + /// + public SMSSettings Tencentyun { get; set; } +} + +public sealed class SMSSettings +{ + /// + /// SdkAppId + /// + public string SdkAppId { get; set; } + + /// + /// AccessKey ID + /// + public string AccessKeyId { get; set; } + + /// + /// AccessKey Secret + /// + public string AccessKeySecret { get; set; } + + /// + /// 短信签名 + /// + public string SignName { get; set; } + + /// + /// 短信模板 + /// + public string TemplateCode { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/SnowIdOptions.cs b/Admin.NET/Admin.NET.Core/Option/SnowIdOptions.cs new file mode 100644 index 00000000..07b2b531 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/SnowIdOptions.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 雪花Id配置选项 +/// +public sealed class SnowIdOptions : IdGeneratorOptions, IConfigurableOptions +{ + /// + /// 缓存前缀 + /// + public string WorkerPrefix { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/UploadOptions.cs b/Admin.NET/Admin.NET.Core/Option/UploadOptions.cs new file mode 100644 index 00000000..83615d61 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/UploadOptions.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using OnceMi.AspNetCore.OSS; + +namespace Admin.NET.Core; + +/// +/// 文件上传配置选项 +/// +public sealed class UploadOptions : IConfigurableOptions +{ + /// + /// 路径 + /// + public string Path { get; set; } + + /// + /// 大小 + /// + public long MaxSize { get; set; } + + /// + /// 上传格式 + /// + public List ContentType { get; set; } + + /// + /// 启用文件MD5验证 + /// + /// 防止重复上传 + public bool EnableMd5 { get; set; } +} + +/// +/// 对象存储配置选项 +/// +public sealed class OSSProviderOptions : OSSOptions, IConfigurableOptions +{ + /// + /// 是否启用OSS存储 + /// + public bool IsEnable { get; set; } + + /// + /// 自定义桶名称 不能直接使用Provider来替代桶名称 + /// 例:阿里云 1.只能包括小写字母,数字,短横线(-)2.必须以小写字母或者数字开头 3.长度必须在3-63字节之间 + /// + public string Bucket { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/WechatOptions.cs b/Admin.NET/Admin.NET.Core/Option/WechatOptions.cs new file mode 100644 index 00000000..1c47b6dc --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/WechatOptions.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 微信相关配置选项 +/// +public sealed class WechatOptions : IConfigurableOptions +{ + // 公众号 + public string WechatAppId { get; set; } + + public string WechatAppSecret { get; set; } + + /// + /// 微信公众号服务器配置中的令牌(Token) + /// + public string WechatToken { get; set; } + + /// + /// 微信公众号服务器配置中的消息加解密密钥(EncodingAESKey) + /// + public string WechatEncodingAESKey { get; set; } + + // 小程序 + public string WxOpenAppId { get; set; } + + public string WxOpenAppSecret { get; set; } + + /// + /// 小程序消息推送中的令牌(Token) + /// + public string WxToken { get; set; } + + /// + /// 小程序消息推送中的消息加解密密钥(EncodingAESKey) + /// + public string WxEncodingAESKey { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Option/WechatPayOptions.cs b/Admin.NET/Admin.NET.Core/Option/WechatPayOptions.cs new file mode 100644 index 00000000..cca5bb60 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Option/WechatPayOptions.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 微信支付配置选项 +/// +public sealed class WechatPayOptions : WechatTenpayClientOptions, IConfigurableOptions +{ + /// + /// 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId + /// + public string AppId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs new file mode 100644 index 00000000..393d110a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs @@ -0,0 +1,42 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统配置表种子数据 +/// +public class SysConfigSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysConfig{ Id=1300000000101, Name="演示环境", Code="sys_demo", Value="False", SysFlag=YesNoEnum.Y, Remark="演示环境", OrderNo=10, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000111, Name="默认密码", Code="sys_password", Value="123456", SysFlag=YesNoEnum.Y, Remark="默认密码", OrderNo=20, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000121, Name="记录操作日志", Code="sys_oplog", Value="True", SysFlag=YesNoEnum.Y, Remark="是否记录操作日志", OrderNo=30, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000131, Name="开启单设备登录", Code="sys_single_login", Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启单设备登录", OrderNo=40, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000141, Name="开启登录二次验证", Code="sys_second_ver", Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启登录二次验证", OrderNo=50, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000151, Name="开启图形验证码", Code="sys_captcha", Value="True", SysFlag=YesNoEnum.Y, Remark="是否开启图形验证码", OrderNo=60, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000161, Name="Token过期时间", Code="sys_token_expire", Value="10080", SysFlag=YesNoEnum.Y, Remark="Token过期时间(分钟)", OrderNo=70, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000171, Name="刷新Token过期时间", Code="sys_refresh_token_expire", Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间(分钟)(一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=80, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000181, Name="发送异常日志邮件", Code="sys_error_mail", Value="True", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=90, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000191, Name="开启域登录验证", Code="sys_domain_login", Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=100, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000211, Name="系统主标题", Code="sys_web_title", Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="系统主标题", OrderNo=110, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000221, Name="系统副标题", Code="sys_web_viceTitle", Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="系统副标题", OrderNo=120, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000231, Name="系统描述", Code="sys_web_viceDesc", Value="站在巨人肩膀上的 .NET 通用权限开发框架", SysFlag=YesNoEnum.Y, Remark="系统描述", OrderNo=130, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000241, Name="水印内容", Code="sys_web_watermark", Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="水印内容", OrderNo=140, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000251, Name="版权说明", Code="sys_web_copyright", Value="Copyright © 2021-present Admin.NET All rights reserved.", SysFlag=YesNoEnum.Y, Remark="版权说明", OrderNo=150, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000261, Name="系统图标", Code="sys_web_logo", Value="/Upload/logo.png", SysFlag=YesNoEnum.Y, Remark="系统图标", OrderNo=160, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000271, Name="ICP备案号", Code="sys_web_icp", Value="京ICP备12345678号", SysFlag=YesNoEnum.Y, Remark="ICP备案号", OrderNo=170, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000281, Name="ICP地址", Code="sys_web_icpUrl", Value="https://beian.miit.gov.cn", SysFlag=YesNoEnum.Y, Remark="ICP地址", OrderNo=180, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysDictDataSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysDictDataSeedData.cs new file mode 100644 index 00000000..b25188d6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysDictDataSeedData.cs @@ -0,0 +1,79 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统字典值表种子数据 +/// +public class SysDictDataSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysDictData{ Id=1300000000101, DictTypeId=1300000000101, Value="输入框", Code="Input", OrderNo=100, Remark="输入框", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000102, DictTypeId=1300000000101, Value="外键", Code="fk", OrderNo=100, Remark="外键", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000103, DictTypeId=1300000000101, Value="时间选择", Code="DatePicker", OrderNo=100, Remark="时间选择", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000104, DictTypeId=1300000000101, Value="选择器", Code="Select", OrderNo=100, Remark="选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000105, DictTypeId=1300000000101, Value="数字输入框", Code="InputNumber", OrderNo=100, Remark="数字输入框", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000106, DictTypeId=1300000000101, Value="文本域", Code="InputTextArea", OrderNo=100, Remark="文本域", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000107, DictTypeId=1300000000101, Value="上传", Code="Upload", OrderNo=100, Remark="上传", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000108, DictTypeId=1300000000101, Value="树选择", Code="ApiTreeSelect", OrderNo=100, Remark="树选择", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000109, DictTypeId=1300000000101, Value="开关", Code="Switch", OrderNo=100, Remark="开关", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000110, DictTypeId=1300000000101, Value="常量选择器", Code="ConstSelector", OrderNo=100, Remark="常量选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000111, DictTypeId=1300000000101, Value="枚举选择器", Code="EnumSelector", OrderNo=100, Remark="枚举选择器", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + + new SysDictData{ Id=1300000000201, DictTypeId=1300000000102, Value="等于", Code="==", OrderNo=1, Remark="等于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000202, DictTypeId=1300000000102, Value="模糊", Code="like", OrderNo=1, Remark="模糊", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000203, DictTypeId=1300000000102, Value="大于", Code=">", OrderNo=1, Remark="大于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000204, DictTypeId=1300000000102, Value="小于", Code="<", OrderNo=1, Remark="小于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000205, DictTypeId=1300000000102, Value="不等于", Code="!=", OrderNo=1, Remark="不等于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000206, DictTypeId=1300000000102, Value="大于等于", Code=">=", OrderNo=1, Remark="大于等于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000207, DictTypeId=1300000000102, Value="小于等于", Code="<=", OrderNo=1, Remark="小于等于", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000208, DictTypeId=1300000000102, Value="不为空", Code="isNotNull", OrderNo=1, Remark="不为空", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000209, DictTypeId=1300000000102, Value="时间范围", Code="~", OrderNo=1, Remark="时间范围", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + + new SysDictData{ Id=1300000000301, DictTypeId=1300000000103, Value="long", Code="long", OrderNo=1, Remark="long", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000302, DictTypeId=1300000000103, Value="string", Code="string", OrderNo=1, Remark="string", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000303, DictTypeId=1300000000103, Value="DateTime", Code="DateTime", OrderNo=1, Remark="DateTime", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000304, DictTypeId=1300000000103, Value="bool", Code="bool", OrderNo=1, Remark="bool", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000305, DictTypeId=1300000000103, Value="int", Code="int", OrderNo=1, Remark="int", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000306, DictTypeId=1300000000103, Value="double", Code="double", OrderNo=1, Remark="double", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000307, DictTypeId=1300000000103, Value="float", Code="float", OrderNo=1, Remark="float", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000308, DictTypeId=1300000000103, Value="decimal", Code="decimal", OrderNo=1, Remark="decimal", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000309, DictTypeId=1300000000103, Value="Guid", Code="Guid", OrderNo=1, Remark="Guid", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000310, DictTypeId=1300000000103, Value="DateTimeOffset", Code="DateTimeOffset", OrderNo=1, Remark="DateTimeOffset", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + + new SysDictData{ Id=1300000000401, DictTypeId=1300000000104, Value="下载压缩包", Code="100", OrderNo=1, Remark="下载压缩包", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000402, DictTypeId=1300000000104, Value="下载压缩包(前端)", Code="111", OrderNo=2, Remark="下载压缩包(前端)", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000403, DictTypeId=1300000000104, Value="下载压缩包(后端)", Code="121", OrderNo=3, Remark="下载压缩包(后端)", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000404, DictTypeId=1300000000104, Value="生成到本项目", Code="200", OrderNo=4, Remark="生成到本项目", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000405, DictTypeId=1300000000104, Value="生成到本项目(前端)", Code="211", OrderNo=5, Remark="生成到本项目(前端)", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000406, DictTypeId=1300000000104, Value="生成到本项目(后端)", Code="221", OrderNo=6, Remark="生成到本项目(后端)", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + + new SysDictData{ Id=1300000000501, DictTypeId=1300000000105, Value="EntityBaseId【基础实体Id】", Code="EntityBaseId", OrderNo=1, Remark="【基础实体Id】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000502, DictTypeId=1300000000105, Value="EntityBase【基础实体】", Code="EntityBase", OrderNo=1, Remark="【基础实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000503, DictTypeId=1300000000105, Value="EntityTenantId【租户实体Id】", Code="EntityTenantId", OrderNo=1, Remark="【租户实体Id】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000504, DictTypeId=1300000000105, Value="EntityTenant【租户实体】", Code="EntityTenant", OrderNo=1, Remark="【租户实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000505, DictTypeId=1300000000105, Value="EntityBaseData【业务实体】", Code="EntityBaseData", OrderNo=1, Remark="【业务实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictData{ Id=1300000000506, DictTypeId=1300000000105, Value="EntityTenantBaseData【租户业务实体】", Code="EntityTenantBaseData", OrderNo=1, Remark="【租户业务实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + + new SysDictData{ Id=1300000000601, DictTypeId=1300000000106, Value="不需要", Code="off", OrderNo=100, Remark="不需要打印支持", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-12-04 00:00:00") }, + new SysDictData{ Id=1300000000602, DictTypeId=1300000000106, Value="绑定打印模版", Code="custom", OrderNo=101, Remark="绑定打印模版", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-12-04 00:00:00") }, + + new SysDictData{ Id=1300000000701, DictTypeId=1300000000201, Value="集团", Code="101", OrderNo=100, Remark="集团", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + new SysDictData{ Id=1300000000702, DictTypeId=1300000000201, Value="公司", Code="201", OrderNo=101, Remark="公司", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + new SysDictData{ Id=1300000000703, DictTypeId=1300000000201, Value="部门", Code="301", OrderNo=102, Remark="部门", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + new SysDictData{ Id=1300000000704, DictTypeId=1300000000201, Value="区域", Code="401", OrderNo=103, Remark="区域", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + new SysDictData{ Id=1300000000705, DictTypeId=1300000000201, Value="组", Code="501", OrderNo=104, Remark="组", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysDictTypeSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysDictTypeSeedData.cs new file mode 100644 index 00000000..9117084d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysDictTypeSeedData.cs @@ -0,0 +1,31 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统字典类型表种子数据 +/// +public class SysDictTypeSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysDictType{ Id=1300000000101, Name="代码生成控件类型", Code="code_gen_effect_type", OrderNo=100, Remark="代码生成控件类型", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictType{ Id=1300000000102, Name="代码生成查询类型", Code="code_gen_query_type", OrderNo=101, Remark="代码生成查询类型", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictType{ Id=1300000000103, Name="代码生成.NET类型", Code="code_gen_net_type", OrderNo=102, Remark="代码生成.NET类型", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictType{ Id=1300000000104, Name="代码生成方式", Code="code_gen_create_type", OrderNo=103, Remark="代码生成方式", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictType{ Id=1300000000105, Name="代码生成基类", Code="code_gen_base_class", OrderNo=104, Remark="代码生成基类", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysDictType{ Id=1300000000106, Name="代码生成打印类型", Code="code_gen_print_type", OrderNo=105, Remark="代码生成打印类型", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-12-04 00:00:00") }, + new SysDictType{ Id=1300000000201, Name="机构类型", Code="org_type", OrderNo=201, Remark="机构类型", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-02-10 00:00:00") }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs new file mode 100644 index 00000000..1ee40f4d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs @@ -0,0 +1,197 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统菜单表种子数据 +/// +public class SysMenuSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysMenu{ Id=1300000000101, Pid=0, Title="工作台", Path="/dashboard", Name="dashboard", Component="Layout", Icon="ele-HomeFilled", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=0 }, + new SysMenu{ Id=1300000000111, Pid=1300000000101, Title="工作台", Path="/dashboard/home", Name="home", Component="/home/index", IsAffix=true, Icon="ele-HomeFilled", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1300000000121, Pid=1300000000101, Title="站内信", Path="/dashboard/notice", Name="notice", Component="/home/notice/index", Icon="ele-Bell", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=101 }, + + // 建议此处Id范围之间放置具体业务应用菜单 + + new SysMenu{ Id=1310000000101, Pid=0, Title="系统管理", Path="/system", Name="system", Component="Layout", Icon="ele-Setting", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=10000 }, + + new SysMenu{ Id=1310000000111, Pid=1310000000101, Title="账号管理", Path="/system/user", Name="sysUser", Component="/system/user/index", Icon="ele-User", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000112, Pid=1310000000111, Title="查询", Permission="sysUser:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000113, Pid=1310000000111, Title="编辑", Permission="sysUser:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000114, Pid=1310000000111, Title="增加", Permission="sysUser:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000115, Pid=1310000000111, Title="删除", Permission="sysUser:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000116, Pid=1310000000111, Title="详情", Permission="sysUser:detail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000117, Pid=1310000000111, Title="授权角色", Permission="sysUser:grantRole", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000118, Pid=1310000000111, Title="重置密码", Permission="sysUser:resetPwd", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000119, Pid=1310000000111, Title="设置状态", Permission="sysUser:setStatus", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000120, Pid=1310000000111, Title="强制下线", Permission="sysOnlineUser:forceOffline", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000121, Pid=1310000000111, Title="解除锁定", Permission="sysUser:unlockLogin", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000131, Pid=1310000000101, Title="角色管理", Path="/system/role", Name="sysRole", Component="/system/role/index", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000132, Pid=1310000000131, Title="查询", Permission="sysRole:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000133, Pid=1310000000131, Title="编辑", Permission="sysRole:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000134, Pid=1310000000131, Title="增加", Permission="sysRole:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000135, Pid=1310000000131, Title="删除", Permission="sysRole:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000136, Pid=1310000000131, Title="授权菜单", Permission="sysRole:grantMenu", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000137, Pid=1310000000131, Title="授权数据", Permission="sysRole:grantDataScope", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000138, Pid=1310000000131, Title="设置状态", Permission="sysRole:setStatus", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000139, Pid=1310000000131, Title="授权接口", Permission="sysRole:grantApi", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000141, Pid=1310000000101, Title="机构管理", Path="/system/org", Name="sysOrg", Component="/system/org/index", Icon="ele-OfficeBuilding", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + //new SysMenu{ Id=1310000000142, Pid=1310000000141, Title="查询", Permission="sysOrg:list", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000143, Pid=1310000000141, Title="编辑", Permission="sysOrg:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000144, Pid=1310000000141, Title="增加", Permission="sysOrg:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000145, Pid=1310000000141, Title="删除", Permission="sysOrg:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000151, Pid=1310000000101, Title="职位管理", Path="/system/pos", Name="sysPos", Component="/system/pos/index",Icon="ele-Mug", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + new SysMenu{ Id=1310000000152, Pid=1310000000151, Title="查询", Permission="sysPos:list", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000153, Pid=1310000000151, Title="编辑", Permission="sysPos:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000154, Pid=1310000000151, Title="增加", Permission="sysPos:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000155, Pid=1310000000151, Title="删除", Permission="sysPos:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000161, Pid=1310000000101, Title="个人中心", Path="/system/userCenter", Name="sysUserCenter", Component="/system/user/component/userCenter",Icon="ele-Medal", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=140 }, + new SysMenu{ Id=1310000000162, Pid=1310000000161, Title="修改密码", Permission="sysUser:changePwd", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000163, Pid=1310000000161, Title="基本信息", Permission="sysUser:baseInfo", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000164, Pid=1310000000161, Title="电子签名", Permission="sysFile:uploadSignature", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000165, Pid=1310000000161, Title="上传头像", Permission="sysFile:uploadAvatar", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000171, Pid=1310000000101, Title="通知公告", Path="/system/notice", Name="sysNotice", Component="/system/notice/index",Icon="ele-Bell", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 }, + new SysMenu{ Id=1310000000172, Pid=1310000000171, Title="查询", Permission="sysNotice:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000173, Pid=1310000000171, Title="编辑", Permission="sysNotice:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000174, Pid=1310000000171, Title="增加", Permission="sysNotice:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000175, Pid=1310000000171, Title="删除", Permission="sysNotice:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000176, Pid=1310000000171, Title="发布", Permission="sysNotice:public", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000177, Pid=1310000000171, Title="撤回", Permission="sysNotice:cancel", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000181, Pid=1310000000101, Title="三方账号", Path="/system/oAuthUser", Name="sysOAuthUser", Component="/system/oAuthUser/index",Icon="ele-ChatDotRound", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=160 }, + new SysMenu{ Id=1310000000182, Pid=1310000000181, Title="查询", Permission="sysOAuthUser:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000183, Pid=1310000000181, Title="编辑", Permission="sysOAuthUser:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000184, Pid=1310000000181, Title="增加", Permission="sysOAuthUser:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000185, Pid=1310000000181, Title="删除", Permission="sysOAuthUser:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000191, Pid=1310000000101, Title="AD域配置", Path="/system/ldap", Name="sysLdap", Component="/system/ldap/index",Icon="ele-Place", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=170 }, + new SysMenu{ Id=1310000000192, Pid=1310000000191, Title="查询", Permission="sysLdap:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000193, Pid=1310000000191, Title="详情", Permission="sysLdap:detail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000194, Pid=1310000000191, Title="编辑", Permission="sysLdap:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + new SysMenu{ Id=1310000000195, Pid=1310000000191, Title="增加", Permission="sysLdap:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + new SysMenu{ Id=1310000000196, Pid=1310000000191, Title="删除", Permission="sysLdap:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=140 }, + new SysMenu{ Id=1310000000197, Pid=1310000000191, Title="同步域账户", Permission="sysLdap:syncUser", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 }, + new SysMenu{ Id=1310000000198, Pid=1310000000191, Title="同步域组织", Permission="sysLdap:syncOrg", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=160 }, + + new SysMenu{ Id=1310000000301, Pid=0, Title="平台管理", Path="/platform", Name="platform", Component="Layout", Icon="ele-Menu", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=11000 }, + + new SysMenu{ Id=1310000000311, Pid=1310000000301, Title="租户管理", Path="/platform/tenant", Name="sysTenant", Component="/system/tenant/index", Icon="ele-School", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000312, Pid=1310000000311, Title="查询", Permission="sysTenant:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000313, Pid=1310000000311, Title="编辑", Permission="sysTenant:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000314, Pid=1310000000311, Title="增加", Permission="sysTenant:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000315, Pid=1310000000311, Title="删除", Permission="sysTenant:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000316, Pid=1310000000311, Title="授权菜单", Permission="sysTenant:grantMenu", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000317, Pid=1310000000311, Title="重置密码", Permission="sysTenant:resetPwd", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000318, Pid=1310000000311, Title="生成库", Permission="sysTenant:createDb", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000319, Pid=1310000000311, Title="设置状态", Permission="sysTenant:setStatus", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000321, Pid=1310000000301, Title="菜单管理", Path="/platform/menu", Name="sysMenu", Component="/system/menu/index", Icon="ele-Menu", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000322, Pid=1310000000321, Title="查询", Permission="sysMenu:list", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000323, Pid=1310000000321, Title="编辑", Permission="sysMenu:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000324, Pid=1310000000321, Title="增加", Permission="sysMenu:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000325, Pid=1310000000321, Title="删除", Permission="sysMenu:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000331, Pid=1310000000301, Title="参数配置", Path="/platform/config", Name="sysConfig", Component="/system/config/index", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + new SysMenu{ Id=1310000000332, Pid=1310000000331, Title="查询", Permission="sysConfig:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000333, Pid=1310000000331, Title="编辑", Permission="sysConfig:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000334, Pid=1310000000331, Title="增加", Permission="sysConfig:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000335, Pid=1310000000331, Title="删除", Permission="sysConfig:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000341, Pid=1310000000301, Title="字典管理", Path="/platform/dict", Name="sysDict", Component="/system/dict/index", Icon="ele-Collection", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + new SysMenu{ Id=1310000000342, Pid=1310000000341, Title="查询", Permission="sysDictType:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000343, Pid=1310000000341, Title="编辑", Permission="sysDictType:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000344, Pid=1310000000341, Title="增加", Permission="sysDictType:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000345, Pid=1310000000341, Title="删除", Permission="sysDictType:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000351, Pid=1310000000301, Title="任务调度", Path="/platform/job", Name="sysJob", Component="/system/job/index", Icon="ele-AlarmClock", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=140 }, + new SysMenu{ Id=1310000000352, Pid=1310000000351, Title="查询", Permission="sysJob:pageJobDetail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000353, Pid=1310000000351, Title="编辑", Permission="sysJob:updateJobDetail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000354, Pid=1310000000351, Title="增加", Permission="sysJob:addJobDetail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000355, Pid=1310000000351, Title="删除", Permission="sysJob:deleteJobDetail", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000361, Pid=1310000000301, Title="系统监控", Path="/platform/server", Name="sysServer", Component="/system/server/index", Icon="ele-Monitor", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 }, + + new SysMenu{ Id=1310000000371, Pid=1310000000301, Title="缓存管理", Path="/platform/cache", Name="sysCache", Component="/system/cache/index", Icon="ele-Loading", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=160 }, + new SysMenu{ Id=1310000000372, Pid=1310000000371, Title="查询", Permission="sysCache:keyList", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000373, Pid=1310000000371, Title="删除", Permission="sysCache:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000381, Pid=1310000000301, Title="行政区域", Path="/platform/region", Name="sysRegion", Component="/system/region/index", Icon="ele-LocationInformation", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=170 }, + new SysMenu{ Id=1310000000382, Pid=1310000000381, Title="查询", Permission="sysRegion:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000383, Pid=1310000000381, Title="编辑", Permission="sysRegion:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000384, Pid=1310000000381, Title="增加", Permission="sysRegion:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000385, Pid=1310000000381, Title="删除", Permission="sysRegion:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000386, Pid=1310000000381, Title="同步", Permission="sysRegion:sync", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000391, Pid=1310000000301, Title="文件管理", Path="/platform/file", Name="sysFile", Component="/system/file/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=180 }, + new SysMenu{ Id=1310000000392, Pid=1310000000391, Title="查询", Permission="sysFile:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000393, Pid=1310000000391, Title="上传", Permission="sysFile:uploadFile", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000394, Pid=1310000000391, Title="下载", Permission="sysFile:downloadFile", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000395, Pid=1310000000391, Title="删除", Permission="sysFile:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000396, Pid=1310000000391, Title="编辑", Permission="sysFile:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2023-10-27 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000401, Pid=1310000000301, Title="打印模板", Path="/platform/print", Name="sysPrint", Component="/system/print/index", Icon="ele-Printer", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=190 }, + new SysMenu{ Id=1310000000402, Pid=1310000000401, Title="查询", Permission="sysPrint:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000403, Pid=1310000000401, Title="编辑", Permission="sysPrint:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000404, Pid=1310000000401, Title="增加", Permission="sysPrint:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000405, Pid=1310000000401, Title="删除", Permission="sysPrint:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000411, Pid=1310000000301, Title="动态插件", Path="/platform/plugin", Name="sysPlugin", Component="/system/plugin/index", Icon="ele-Connection", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=200 }, + new SysMenu{ Id=1310000000412, Pid=1310000000411, Title="查询", Permission="sysPlugin:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000413, Pid=1310000000411, Title="编辑", Permission="sysPlugin:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000414, Pid=1310000000411, Title="增加", Permission="sysPlugin:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000415, Pid=1310000000411, Title="删除", Permission="sysPlugin:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000421, Pid=1310000000301, Title="开放接口", Path="/platform/openAccess", Name="sysOpenAccess", Component="/system/openAccess/index", Icon="ele-Link", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=210 }, + new SysMenu{ Id=1310000000422, Pid=1310000000421, Title="查询", Permission="sysOpenAccess:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000423, Pid=1310000000421, Title="编辑", Permission="sysOpenAccess:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000424, Pid=1310000000421, Title="增加", Permission="sysOpenAccess:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000425, Pid=1310000000421, Title="删除", Permission="sysOpenAccess:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000431, Pid=1310000000301, Title="系统配置", Path="/platform/infoSetting", Name="sysInfoSetting", Component="/system/infoSetting/index", Icon="ele-Setting", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=220 }, + + new SysMenu{ Id=1310000000501, Pid=0, Title="日志管理", Path="/log", Name="log", Component="Layout", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=12000 }, + new SysMenu{ Id=1310000000511, Pid=1310000000501, Title="访问日志", Path="/log/vislog", Name="sysVisLog", Component="/system/log/vislog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000512, Pid=1310000000511, Title="查询", Permission="sysVislog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000513, Pid=1310000000511, Title="清空", Permission="sysVislog:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000521, Pid=1310000000501, Title="操作日志", Path="/log/oplog", Name="sysOpLog", Component="/system/log/oplog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000522, Pid=1310000000521, Title="查询", Permission="sysOplog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000523, Pid=1310000000521, Title="清空", Permission="sysOplog:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000524, Pid=1310000000521, Title="导出", Permission="sysOplog:export", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000531, Pid=1310000000501, Title="异常日志", Path="/log/exlog", Name="sysExLog", Component="/system/log/exlog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + new SysMenu{ Id=1310000000532, Pid=1310000000531, Title="查询", Permission="sysExlog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000533, Pid=1310000000531, Title="清空", Permission="sysExlog:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000534, Pid=1310000000531, Title="导出", Permission="sysExlog:export", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000541, Pid=1310000000501, Title="差异日志", Path="/log/difflog", Name="sysDiffLog", Component="/system/log/difflog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + new SysMenu{ Id=1310000000542, Pid=1310000000541, Title="查询", Permission="sysDifflog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000543, Pid=1310000000541, Title="清空", Permission="sysDifflog:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + + new SysMenu{ Id=1310000000601, Pid=0, Title="开发工具", Path="/develop", Name="develop", Component="Layout", Icon="ele-Cpu", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=13000 }, + new SysMenu{ Id=1310000000611, Pid=1310000000601, Title="库表管理", Path="/develop/database", Name="sysDatabase", Component="/system/database/index",Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000621, Pid=1310000000601, Title="代码生成", Path="/develop/codeGen", Name="sysCodeGen", Component="/system/codeGen/index", Icon="ele-Crop", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000631, Pid=1310000000601, Title="表单设计", Path="/develop/formDes", Name="sysFormDes", Component="/system/formDes/index", Icon="ele-Edit", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + new SysMenu{ Id=1310000000641, Pid=1310000000601, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 }, + + new SysMenu{ Id=1310000000701, Pid=0, Title="帮助文档", Path="/doc", Name="doc", Component="Layout", Icon="ele-Notebook", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=14000 }, + new SysMenu{ Id=1310000000711, Pid=1310000000701, Title="后台教程", Path="/doc/furion", Name="sysFurion", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://furion.baiqian.ltd/", Icon="ele-Promotion", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 }, + new SysMenu{ Id=1310000000712, Pid=1310000000701, Title="前端教程", Path="/doc/element", Name="sysElement", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://element-plus.gitee.io/zh-CN/", Icon="ele-Position", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 }, + new SysMenu{ Id=1310000000713, Pid=1310000000701, Title="SqlSugar", Path="/doc/SqlSugar", Name="sysSqlSugar", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://www.donet5.com/Home/Doc", Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysOrgSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysOrgSeedData.cs new file mode 100644 index 00000000..56db4380 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysOrgSeedData.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统机构表种子数据 +/// +[IgnoreUpdateSeed] +public class SysOrgSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysOrg{ Id=1300000000101, Pid=0, Name="XXX公司", Code="1001", Type="101", Level=1, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="XXX公司", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000102, Pid=1300000000101, Name="市场部", Code="100101", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="市场部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000103, Pid=1300000000101, Name="研发部", Code="100102", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="研发部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000104, Pid=1300000000101, Name="财务部", Code="100103", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="财务部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000105, Pid=1300000000104, Name="财务部1", Code="10010301", Level=3, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="财务部1", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000106, Pid=1300000000104, Name="财务部2", Code="10010302", Level=3, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="财务部2", TenantId=SqlSugarConst.DefaultTenantId }, + + new SysOrg{ Id=1300000000201, Pid=0, Name="分公司1", Code="1002", Type="201", Level=1, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="分公司1", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000202, Pid=1300000000201, Name="市场部", Code="100201", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="市场部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000203, Pid=1300000000201, Name="研发部", Code="100202", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="研发部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000204, Pid=1300000000201, Name="财务部", Code="100203", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="财务部", TenantId=SqlSugarConst.DefaultTenantId }, + + new SysOrg{ Id=1300000000301, Pid=0, Name="分公司2", Code="1003", Type="201", Level=1, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="分公司2", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000302, Pid=1300000000301, Name="市场部", Code="100301", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="市场部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000303, Pid=1300000000301, Name="研发部", Code="100302", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="市场部", TenantId=SqlSugarConst.DefaultTenantId }, + new SysOrg{ Id=1300000000304, Pid=1300000000301, Name="财务部", Code="100303", Level=2, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="市场部", TenantId=SqlSugarConst.DefaultTenantId }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysPosSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysPosSeedData.cs new file mode 100644 index 00000000..64b62e65 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysPosSeedData.cs @@ -0,0 +1,40 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统职位表种子数据 +/// +public class SysPosSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysPos{ Id=1300000000101, Name="党委书记", Code="dwsj", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="党委书记", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000102, Name="董事长", Code="dsz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="董事长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000103, Name="副董事长", Code="fdsz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="副董事长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000104, Name="总经理", Code="zjl", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="总经理", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000105, Name="副总经理", Code="fzjl", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="副总经理", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000106, Name="部门经理", Code="bmjl", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="部门经理", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000107, Name="部门副经理", Code="bmfjl", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="部门副经理", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000108, Name="主任", Code="zr", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="主任", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000109, Name="副主任", Code="fzr", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="副主任", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000110, Name="局长", Code="jz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="局长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000111, Name="副局长", Code="fjz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="副局长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000112, Name="科长", Code="kz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="科长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000113, Name="副科长", Code="fkz", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="副科长", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000114, Name="财务", Code="cw", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="财务", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000115, Name="职员", Code="zy", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="职员", TenantId=SqlSugarConst.DefaultTenantId }, + new SysPos{ Id=1300000000116, Name="其他", Code="qt", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="其他", TenantId=SqlSugarConst.DefaultTenantId }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysRoleApiSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysRoleApiSeedData.cs new file mode 100644 index 00000000..e783134d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysRoleApiSeedData.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色接口资源表种子数据 +/// +public class SysRoleApiSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + var index = 0; + + var sysRoleApis = new List(); + foreach (var route in CommonConst.SysBaseRoutes) + { + var id = 1300000000101 + index * 4; + + // 本部门及以下数据 + sysRoleApis.Add(new SysRoleApi { Id = id + 1, RoleId = 1300000000102, Route = route }); + // 本部门数据 + sysRoleApis.Add(new SysRoleApi { Id = id + 2, RoleId = 1300000000103, Route = route }); + // 仅本人数据 + sysRoleApis.Add(new SysRoleApi { Id = id + 3, RoleId = 1300000000104, Route = route }); + // 自定义数据 + sysRoleApis.Add(new SysRoleApi { Id = id + 4, RoleId = 1300000000105, Route = route }); + + index++; + } + + return sysRoleApis; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysRoleMenuSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysRoleMenuSeedData.cs new file mode 100644 index 00000000..4b125c95 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysRoleMenuSeedData.cs @@ -0,0 +1,183 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色菜单表种子数据 +/// +public class SysRoleMenuSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + // 数据面板【admin/1300000000101】 + new SysRoleMenu{ Id=1300000000101, RoleId=1300000000101, MenuId=1300000000101 }, + new SysRoleMenu{ Id=1300000000102, RoleId=1300000000101, MenuId=1300000000111 }, + new SysRoleMenu{ Id=1300000000103, RoleId=1300000000101, MenuId=1300000000121 }, + + // 系统管理 + new SysRoleMenu{ Id=1300000000111, RoleId=1300000000101, MenuId=1310000000101 }, + // 账号管理 + new SysRoleMenu{ Id=1300000000121, RoleId=1300000000101, MenuId=1310000000111 }, + new SysRoleMenu{ Id=1300000000122, RoleId=1300000000101, MenuId=1310000000112 }, + new SysRoleMenu{ Id=1300000000123, RoleId=1300000000101, MenuId=1310000000113 }, + new SysRoleMenu{ Id=1300000000124, RoleId=1300000000101, MenuId=1310000000114 }, + new SysRoleMenu{ Id=1300000000125, RoleId=1300000000101, MenuId=1310000000115 }, + new SysRoleMenu{ Id=1300000000126, RoleId=1300000000101, MenuId=1310000000116 }, + new SysRoleMenu{ Id=1300000000127, RoleId=1300000000101, MenuId=1310000000117 }, + new SysRoleMenu{ Id=1300000000128, RoleId=1300000000101, MenuId=1310000000118 }, + new SysRoleMenu{ Id=1300000000129, RoleId=1300000000101, MenuId=1310000000119 }, + new SysRoleMenu{ Id=1300000000130, RoleId=1300000000101, MenuId=1310000000120 }, + // 角色管理 + new SysRoleMenu{ Id=1300000000141, RoleId=1300000000101, MenuId=1310000000131 }, + new SysRoleMenu{ Id=1300000000142, RoleId=1300000000101, MenuId=1310000000132 }, + new SysRoleMenu{ Id=1300000000143, RoleId=1300000000101, MenuId=1310000000133 }, + new SysRoleMenu{ Id=1300000000144, RoleId=1300000000101, MenuId=1310000000134 }, + new SysRoleMenu{ Id=1300000000145, RoleId=1300000000101, MenuId=1310000000135 }, + new SysRoleMenu{ Id=1300000000146, RoleId=1300000000101, MenuId=1310000000136 }, + new SysRoleMenu{ Id=1300000000147, RoleId=1300000000101, MenuId=1310000000137 }, + new SysRoleMenu{ Id=1300000000148, RoleId=1300000000101, MenuId=1310000000138 }, + // 机构管理 + new SysRoleMenu{ Id=1300000000151, RoleId=1300000000101, MenuId=1310000000141 }, + new SysRoleMenu{ Id=1300000000152, RoleId=1300000000101, MenuId=1310000000142 }, + new SysRoleMenu{ Id=1300000000153, RoleId=1300000000101, MenuId=1310000000143 }, + new SysRoleMenu{ Id=1300000000154, RoleId=1300000000101, MenuId=1310000000144 }, + new SysRoleMenu{ Id=1300000000155, RoleId=1300000000101, MenuId=1310000000145 }, + // 职位管理 + new SysRoleMenu{ Id=1300000000161, RoleId=1300000000101, MenuId=1310000000151 }, + new SysRoleMenu{ Id=1300000000162, RoleId=1300000000101, MenuId=1310000000152 }, + new SysRoleMenu{ Id=1300000000163, RoleId=1300000000101, MenuId=1310000000153 }, + new SysRoleMenu{ Id=1300000000164, RoleId=1300000000101, MenuId=1310000000154 }, + new SysRoleMenu{ Id=1300000000165, RoleId=1300000000101, MenuId=1310000000155 }, + // 个人中心 + new SysRoleMenu{ Id=1300000000171, RoleId=1300000000101, MenuId=1310000000161 }, + new SysRoleMenu{ Id=1300000000172, RoleId=1300000000101, MenuId=1310000000162 }, + new SysRoleMenu{ Id=1300000000173, RoleId=1300000000101, MenuId=1310000000163 }, + new SysRoleMenu{ Id=1300000000174, RoleId=1300000000101, MenuId=1310000000164 }, + new SysRoleMenu{ Id=1300000000175, RoleId=1300000000101, MenuId=1310000000165 }, + // 通知公告 + new SysRoleMenu{ Id=1300000000181, RoleId=1300000000101, MenuId=1310000000171 }, + new SysRoleMenu{ Id=1300000000182, RoleId=1300000000101, MenuId=1310000000172 }, + new SysRoleMenu{ Id=1300000000183, RoleId=1300000000101, MenuId=1310000000173 }, + new SysRoleMenu{ Id=1300000000184, RoleId=1300000000101, MenuId=1310000000174 }, + new SysRoleMenu{ Id=1300000000185, RoleId=1300000000101, MenuId=1310000000175 }, + new SysRoleMenu{ Id=1300000000186, RoleId=1300000000101, MenuId=1310000000176 }, + new SysRoleMenu{ Id=1300000000187, RoleId=1300000000101, MenuId=1310000000177 }, + // 三方账号 + new SysRoleMenu{ Id=1300000000191, RoleId=1300000000101, MenuId=1310000000181 }, + new SysRoleMenu{ Id=1300000000192, RoleId=1300000000101, MenuId=1310000000182 }, + new SysRoleMenu{ Id=1300000000193, RoleId=1300000000101, MenuId=1310000000183 }, + new SysRoleMenu{ Id=1300000000194, RoleId=1300000000101, MenuId=1310000000184 }, + new SysRoleMenu{ Id=1300000000195, RoleId=1300000000101, MenuId=1310000000185 }, + + //// 平台管理 + //new SysRoleMenu{ Id=1300000000201, RoleId=1300000000101, MenuId=1310000000301 }, + // 任务调度 + new SysRoleMenu{ Id=1300000000251, RoleId=1300000000101, MenuId=1310000000351 }, + new SysRoleMenu{ Id=1300000000252, RoleId=1300000000101, MenuId=1310000000352 }, + new SysRoleMenu{ Id=1300000000253, RoleId=1300000000101, MenuId=1310000000353 }, + new SysRoleMenu{ Id=1300000000254, RoleId=1300000000101, MenuId=1310000000354 }, + new SysRoleMenu{ Id=1300000000255, RoleId=1300000000101, MenuId=1310000000355 }, + // 系统监控 + new SysRoleMenu{ Id=1300000000261, RoleId=1300000000101, MenuId=1310000000361 }, + // 缓存管理 + new SysRoleMenu{ Id=1300000000271, RoleId=1300000000101, MenuId=1310000000371 }, + new SysRoleMenu{ Id=1300000000272, RoleId=1300000000101, MenuId=1310000000372 }, + new SysRoleMenu{ Id=1300000000273, RoleId=1300000000101, MenuId=1310000000373 }, + // 行政区域 + new SysRoleMenu{ Id=1300000000281, RoleId=1300000000101, MenuId=1310000000381 }, + new SysRoleMenu{ Id=1300000000282, RoleId=1300000000101, MenuId=1310000000382 }, + new SysRoleMenu{ Id=1300000000283, RoleId=1300000000101, MenuId=1310000000383 }, + new SysRoleMenu{ Id=1300000000284, RoleId=1300000000101, MenuId=1310000000384 }, + new SysRoleMenu{ Id=1300000000285, RoleId=1300000000101, MenuId=1310000000385 }, + new SysRoleMenu{ Id=1300000000286, RoleId=1300000000101, MenuId=1310000000386 }, + // 文件管理 + new SysRoleMenu{ Id=1300000000291, RoleId=1300000000101, MenuId=1310000000391 }, + new SysRoleMenu{ Id=1300000000292, RoleId=1300000000101, MenuId=1310000000392 }, + new SysRoleMenu{ Id=1300000000293, RoleId=1300000000101, MenuId=1310000000393 }, + new SysRoleMenu{ Id=1300000000294, RoleId=1300000000101, MenuId=1310000000394 }, + new SysRoleMenu{ Id=1300000000295, RoleId=1300000000101, MenuId=1310000000395 }, + new SysRoleMenu{ Id=1300000000296, RoleId=1300000000101, MenuId=1310000000396 }, + + //// 日志管理 + //new SysRoleMenu{ Id=1300000000301, RoleId=1300000000101, MenuId=1310000000501 }, + new SysRoleMenu{ Id=1300000000311, RoleId=1300000000101, MenuId=1310000000511 }, + new SysRoleMenu{ Id=1300000000312, RoleId=1300000000101, MenuId=1310000000512 }, + new SysRoleMenu{ Id=1300000000313, RoleId=1300000000101, MenuId=1310000000513 }, + new SysRoleMenu{ Id=1300000000321, RoleId=1300000000101, MenuId=1310000000521 }, + new SysRoleMenu{ Id=1300000000322, RoleId=1300000000101, MenuId=1310000000522 }, + new SysRoleMenu{ Id=1300000000323, RoleId=1300000000101, MenuId=1310000000523 }, + new SysRoleMenu{ Id=1300000000324, RoleId=1300000000101, MenuId=1310000000524 }, + new SysRoleMenu{ Id=1300000000331, RoleId=1300000000101, MenuId=1310000000531 }, + new SysRoleMenu{ Id=1300000000332, RoleId=1300000000101, MenuId=1310000000532 }, + new SysRoleMenu{ Id=1300000000333, RoleId=1300000000101, MenuId=1310000000543 }, + + // 帮助文档 + new SysRoleMenu{ Id=1300000000401, RoleId=1300000000101, MenuId=1310000000701 }, + new SysRoleMenu{ Id=1300000000402, RoleId=1300000000101, MenuId=1310000000711 }, + new SysRoleMenu{ Id=1300000000403, RoleId=1300000000101, MenuId=1310000000712 }, + + // 其他角色默认菜单 + // 数据面板【1300000000102】 + new SysRoleMenu{ Id=1300000000501, RoleId=1300000000102, MenuId=1300000000101 }, + new SysRoleMenu{ Id=1300000000502, RoleId=1300000000102, MenuId=1300000000111 }, + new SysRoleMenu{ Id=1300000000503, RoleId=1300000000102, MenuId=1300000000121 }, + // 机构管理 + new SysRoleMenu{ Id=1300000000511, RoleId=1300000000102, MenuId=1310000000142 }, + // 个人中心 + new SysRoleMenu{ Id=1300000000521, RoleId=1300000000102, MenuId=1310000000161 }, + new SysRoleMenu{ Id=1300000000522, RoleId=1300000000102, MenuId=1310000000162 }, + new SysRoleMenu{ Id=1300000000523, RoleId=1300000000102, MenuId=1310000000163 }, + new SysRoleMenu{ Id=1300000000524, RoleId=1300000000102, MenuId=1310000000164 }, + new SysRoleMenu{ Id=1300000000525, RoleId=1300000000102, MenuId=1310000000165 }, + + // 数据面板【1300000000103】 + new SysRoleMenu{ Id=1300000000601, RoleId=1300000000103, MenuId=1300000000101 }, + new SysRoleMenu{ Id=1300000000602, RoleId=1300000000103, MenuId=1300000000111 }, + new SysRoleMenu{ Id=1300000000603, RoleId=1300000000103, MenuId=1300000000121 }, + // 机构管理 + new SysRoleMenu{ Id=1300000000611, RoleId=1300000000103, MenuId=1310000000142 }, + // 个人中心 + new SysRoleMenu{ Id=1300000000621, RoleId=1300000000103, MenuId=1310000000161 }, + new SysRoleMenu{ Id=1300000000622, RoleId=1300000000103, MenuId=1310000000162 }, + new SysRoleMenu{ Id=1300000000623, RoleId=1300000000103, MenuId=1310000000163 }, + new SysRoleMenu{ Id=1300000000624, RoleId=1300000000103, MenuId=1310000000164 }, + new SysRoleMenu{ Id=1300000000625, RoleId=1300000000103, MenuId=1310000000165 }, + + // 数据面板【1300000000104】 + new SysRoleMenu{ Id=1300000000701, RoleId=1300000000104, MenuId=1300000000101 }, + new SysRoleMenu{ Id=1300000000702, RoleId=1300000000104, MenuId=1300000000111 }, + new SysRoleMenu{ Id=1300000000703, RoleId=1300000000104, MenuId=1300000000121 }, + // 机构管理 + new SysRoleMenu{ Id=1300000000711, RoleId=1300000000104, MenuId=1310000000142 }, + // 个人中心 + new SysRoleMenu{ Id=1300000000721, RoleId=1300000000104, MenuId=1310000000161 }, + new SysRoleMenu{ Id=1300000000722, RoleId=1300000000104, MenuId=1310000000162 }, + new SysRoleMenu{ Id=1300000000723, RoleId=1300000000104, MenuId=1310000000163 }, + new SysRoleMenu{ Id=1300000000724, RoleId=1300000000104, MenuId=1310000000164 }, + new SysRoleMenu{ Id=1300000000725, RoleId=1300000000104, MenuId=1310000000165 }, + + // 数据面板【1300000000105】 + new SysRoleMenu{ Id=1300000000801, RoleId=1300000000105, MenuId=1300000000101 }, + new SysRoleMenu{ Id=1300000000802, RoleId=1300000000105, MenuId=1300000000111 }, + new SysRoleMenu{ Id=1300000000803, RoleId=1300000000105, MenuId=1300000000121 }, + // 机构管理 + new SysRoleMenu{ Id=1300000000811, RoleId=1300000000105, MenuId=1310000000142 }, + // 个人中心 + new SysRoleMenu{ Id=1300000000821, RoleId=1300000000105, MenuId=1310000000161 }, + new SysRoleMenu{ Id=1300000000822, RoleId=1300000000105, MenuId=1310000000162 }, + new SysRoleMenu{ Id=1300000000823, RoleId=1300000000105, MenuId=1310000000163 }, + new SysRoleMenu{ Id=1300000000824, RoleId=1300000000105, MenuId=1310000000164 }, + new SysRoleMenu{ Id=1300000000825, RoleId=1300000000105, MenuId=1310000000165 }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysRoleSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysRoleSeedData.cs new file mode 100644 index 00000000..5dc8c8fe --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysRoleSeedData.cs @@ -0,0 +1,29 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统角色表种子数据 +/// +public class SysRoleSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysRole{ Id=1300000000101, Name="系统管理员", DataScope=DataScopeEnum.All, Code="sys_admin", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="系统管理员", TenantId=SqlSugarConst.DefaultTenantId }, + new SysRole{ Id=1300000000102, Name="本部门及以下数据", DataScope=DataScopeEnum.DeptChild, Code="sys_deptChild", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="本部门及以下数据", TenantId=SqlSugarConst.DefaultTenantId }, + new SysRole{ Id=1300000000103, Name="本部门数据", DataScope=DataScopeEnum.Dept, Code="sys_dept", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="本部门数据", TenantId=SqlSugarConst.DefaultTenantId }, + new SysRole{ Id=1300000000104, Name="仅本人数据", DataScope=DataScopeEnum.Self, Code="sys_self", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="仅本人数据", TenantId=SqlSugarConst.DefaultTenantId }, + new SysRole{ Id=1300000000105, Name="自定义数据", DataScope=DataScopeEnum.Define, Code="sys_define", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Remark="自定义数据", TenantId=SqlSugarConst.DefaultTenantId }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysTenantSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysTenantSeedData.cs new file mode 100644 index 00000000..376b525e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysTenantSeedData.cs @@ -0,0 +1,27 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统租户表种子数据 +/// +public class SysTenantSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + var defaultDbConfig = App.GetOptions().ConnectionConfigs[0]; + + return new[] + { + new SysTenant{ Id=SqlSugarConst.DefaultTenantId, OrgId=1300000000101, UserId=1300000000111, Host="https://gitee.com", TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysUserExtOrgSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysUserExtOrgSeedData.cs new file mode 100644 index 00000000..c3f4bbdf --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysUserExtOrgSeedData.cs @@ -0,0 +1,26 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户扩展机构表种子数据 +/// +public class SysUserExtOrgSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysUserExtOrg{ Id=1300000000101, UserId=1300000000111, OrgId=1300000000202, PosId=1300000000106 }, + new SysUserExtOrg{ Id=1300000000102, UserId=1300000000114, OrgId=1300000000302, PosId=1300000000108 } + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysUserRoleSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysUserRoleSeedData.cs new file mode 100644 index 00000000..8f34bd0e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysUserRoleSeedData.cs @@ -0,0 +1,29 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户角色表种子数据 +/// +public class SysUserRoleSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + return new[] + { + new SysUserRole{ Id=1300000000101, UserId=1300000000111, RoleId=1300000000101 }, + new SysUserRole{ Id=1300000000102, UserId=1300000000112, RoleId=1300000000102 }, + new SysUserRole{ Id=1300000000103, UserId=1300000000113, RoleId=1300000000103 }, + new SysUserRole{ Id=1300000000104, UserId=1300000000114, RoleId=1300000000104 }, + new SysUserRole{ Id=1300000000105, UserId=1300000000115, RoleId=1300000000105 }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs new file mode 100644 index 00000000..a809c235 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统用户表种子数据 +/// +[IgnoreUpdateSeed] +public class SysUserSeedData : ISqlSugarEntitySeedData +{ + /// + /// 种子数据 + /// + /// + public IEnumerable HasData() + { + var encryptPassword = CryptogramUtil.Encrypt("123456"); + + return new[] + { + new SysUser{ Id=1300000000101, Account="superadmin", Password=encryptPassword, NickName="超级管理员", RealName="超级管理员", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Male, AccountType=AccountTypeEnum.SuperAdmin, Remark="超级管理员", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), TenantId=SqlSugarConst.DefaultTenantId }, + new SysUser{ Id=1300000000111, Account="admin", Password=encryptPassword, NickName="系统管理员", RealName="系统管理员", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Male, AccountType=AccountTypeEnum.SysAdmin, Remark="系统管理员", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrgId=1300000000101, PosId=1300000000102, TenantId=SqlSugarConst.DefaultTenantId }, + new SysUser{ Id=1300000000112, Account="user1", Password=encryptPassword, NickName="部门主管", RealName="部门主管", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Female, AccountType=AccountTypeEnum.NormalUser, Remark="部门主管", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrgId=1300000000102, PosId=1300000000108, TenantId=SqlSugarConst.DefaultTenantId }, + new SysUser{ Id=1300000000113, Account="user2", Password=encryptPassword, NickName="部门职员", RealName="部门职员", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Female, AccountType=AccountTypeEnum.NormalUser, Remark="部门职员", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrgId=1300000000103, PosId=1300000000110, TenantId=SqlSugarConst.DefaultTenantId }, + new SysUser{ Id=1300000000114, Account="user3", Password=encryptPassword, NickName="普通用户", RealName="普通用户", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Female, AccountType=AccountTypeEnum.NormalUser, Remark="普通用户", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrgId=1300000000104, PosId=1300000000115, TenantId=SqlSugarConst.DefaultTenantId }, + new SysUser{ Id=1300000000115, Account="user4", Password=encryptPassword, NickName="其他", RealName="其他", Phone="18012345678", Birthday=DateTime.Parse("2000-01-01"), Sex=GenderEnum.Female, AccountType=AccountTypeEnum.Member, Remark="会员", CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrgId=1300000000105, PosId=1300000000116, TenantId=SqlSugarConst.DefaultTenantId }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/APIJSONService.cs b/Admin.NET/Admin.NET.Core/Service/APIJSON/APIJSONService.cs new file mode 100644 index 00000000..8a86418a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/APIJSONService.cs @@ -0,0 +1,206 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// APIJSON服务 🧩 +/// +[ApiDescriptionSettings(Order = 100)] +public class APIJSONService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + private readonly IdentityService _identityService; + private readonly TableMapper _tableMapper; + private readonly SelectTable _selectTable; + + public APIJSONService(ISqlSugarClient db, + IdentityService identityService, + TableMapper tableMapper) + { + _db = db; + _tableMapper = tableMapper; + _identityService = identityService; + _selectTable = new SelectTable(_identityService, _tableMapper, _db); + } + + /// + /// 统一查询入口 🔖 + /// + /// + /// 参数:{"[]":{"SYSLOGOP":{}}} + /// + [HttpPost("get")] + [DisplayName("APIJSON统一查询")] + public JObject Query([FromBody] JObject jobject) + { + return _selectTable.Query(jobject); + } + + /// + /// 查询 🔖 + /// + /// + /// + /// + [HttpPost("get/{table}")] + [DisplayName("APIJSON查询")] + public JObject QueryByTable([FromRoute] string table, [FromBody] JObject jobject) + { + var ht = new JObject + { + { table + "[]", jobject } + }; + + // 自动添加总计数量 + if (jobject["query"] != null && jobject["query"].ToString() != "0" && jobject["total@"] == null) + ht.Add("total@", ""); + + // 每页最大1000条数据 + if (jobject["count"] != null && int.Parse(jobject["count"].ToString()) > 1000) + throw Oops.Bah("count分页数量最大不能超过1000"); + + jobject.Remove("@debug"); + + var hasTableKey = false; + var ignoreConditions = new List { "page", "count", "query" }; + var tableConditions = new JObject(); // 表的其它查询条件,比如过滤、字段等 + foreach (var item in jobject) + { + if (item.Key.Equals(table, StringComparison.CurrentCultureIgnoreCase)) + { + hasTableKey = true; + break; + } + if (!ignoreConditions.Contains(item.Key.ToLower())) + tableConditions.Add(item.Key, item.Value); + } + + foreach (var removeKey in tableConditions) + { + jobject.Remove(removeKey.Key); + } + + if (!hasTableKey) + jobject.Add(table, tableConditions); + + return Query(ht); + } + + /// + /// 新增 🔖 + /// + /// 表对象或数组,若没有传Id则后端生成Id + /// + [HttpPost("add")] + [DisplayName("APIJSON新增")] + [UnitOfWork] + public JObject Add([FromBody] JObject tables) + { + var ht = new JObject(); + foreach (var table in tables) + { + var talbeName = table.Key.Trim(); + var role = _identityService.GetRole(); + if (!role.Insert.Table.Contains(talbeName, StringComparer.CurrentCultureIgnoreCase)) + throw Oops.Bah($"没权限添加{talbeName}"); + + JToken result; + // 批量插入 + if (table.Value is JArray) + { + var ids = new List(); + foreach (var record in table.Value) + { + var cols = record.ToObject(); + var id = _selectTable.InsertSingle(talbeName, cols, role); + ids.Add(id); + } + result = JToken.FromObject(new { id = ids, count = ids.Count }); + } + // 单条插入 + else + { + var cols = table.Value.ToObject(); + var id = _selectTable.InsertSingle(talbeName, cols, role); + result = JToken.FromObject(new { id }); + } + ht.Add(talbeName, result); + } + return ht; + } + + /// + /// 更新(只支持Id作为条件) 🔖 + /// + /// 支持多表、多Id批量更新 + /// + [HttpPost("update")] + [DisplayName("APIJSON更新")] + [UnitOfWork] + public JObject Edit([FromBody] JObject tables) + { + var ht = new JObject(); + foreach (var table in tables) + { + var tableName = table.Key.Trim(); + var role = _identityService.GetRole(); + var count = _selectTable.UpdateSingleTable(tableName, table.Value, role); + ht.Add(tableName, JToken.FromObject(new { count })); + } + return ht; + } + + /// + /// 删除(支持非Id条件、支持批量) 🔖 + /// + /// + /// + [HttpPost("delete")] + [DisplayName("APIJSON删除")] + [UnitOfWork] + public JObject Delete([FromBody] JObject tables) + { + var ht = new JObject(); + var role = _identityService.GetRole(); + foreach (var table in tables) + { + var talbeName = table.Key.Trim(); + if (role.Delete == null || role.Delete.Table == null) + throw Oops.Bah("delete权限未配置"); + if (!role.Delete.Table.Contains(talbeName, StringComparer.CurrentCultureIgnoreCase)) + throw Oops.Bah($"没权限删除{talbeName}"); + //if (!value.ContainsKey("id")) + // throw Oops.Bah("未传主键id"); + + var value = JObject.Parse(table.Value.ToString()); + var sb = new StringBuilder(100); + var parameters = new List(); + foreach (var f in value) + { + if (f.Value is JArray) + { + sb.Append($"{f.Key} in (@{f.Key}) and "); + var paraArray = FuncList.TransJArrayToSugarPara(f.Value); + parameters.Add(new SugarParameter($"@{f.Key}", paraArray)); + } + else + { + sb.Append($"{f.Key}=@{f.Key} and "); + parameters.Add(new SugarParameter($"@{f.Key}", FuncList.TransJObjectToSugarPara(f.Value))); + } + } + if (!parameters.Any()) + throw Oops.Bah("请输入删除条件"); + + var whereSql = sb.ToString().TrimEnd(" and "); + var count = _db.Deleteable().AS(talbeName).Where(whereSql, parameters).ExecuteCommand(); // 无实体删除 + value.Add("count", count); // 命中数量 + ht.Add(talbeName, value); + } + return ht; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/FuncList.cs b/Admin.NET/Admin.NET.Core/Service/APIJSON/FuncList.cs new file mode 100644 index 00000000..139e02f3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/FuncList.cs @@ -0,0 +1,116 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 自定义方法 +/// +public class FuncList +{ + /// + /// 字符串相加 + /// + /// + /// + /// + public string Merge(object a, object b) + { + return a.ToString() + b.ToString(); + } + + /// + /// 对象合并 + /// + /// + /// + /// + public object MergeObj(object a, object b) + { + return new { a, b }; + } + + /// + /// 是否包含 + /// + /// + /// + /// + public bool IsContain(object a, object b) + { + return a.ToString().Split(',').Contains(b); + } + + /// + /// 根据jtoken的实际类型来转换SugarParameter,避免全转成字符串 + /// + /// + /// + public static dynamic TransJObjectToSugarPara(JToken jToken) + { + JTokenType jTokenType = jToken.Type; + return jTokenType switch + { + JTokenType.Integer => jToken.ToObject(typeof(long)), + JTokenType.Float => jToken.ToObject(typeof(decimal)), + JTokenType.Boolean => jToken.ToObject(typeof(bool)), + JTokenType.Date => jToken.ToObject(typeof(DateTime)), + JTokenType.Bytes => jToken.ToObject(typeof(byte)), + JTokenType.Guid => jToken.ToObject(typeof(Guid)), + JTokenType.TimeSpan => jToken.ToObject(typeof(TimeSpan)), + JTokenType.Array => TransJArrayToSugarPara(jToken), + _ => jToken + }; + } + + /// + /// 根据jArray的实际类型来转换SugarParameter,避免全转成字符串 + /// + /// + /// + public static dynamic TransJArrayToSugarPara(JToken jToken) + { + if (jToken is not JArray) return jToken; + if (jToken.Any()) + { + JTokenType jTokenType = jToken.First().Type; + return jTokenType switch + { + JTokenType.Integer => jToken.ToObject(), + JTokenType.Float => jToken.ToObject(), + JTokenType.Boolean => jToken.ToObject(), + JTokenType.Date => jToken.ToObject(), + JTokenType.Bytes => jToken.ToObject(), + JTokenType.Guid => jToken.ToObject(), + JTokenType.TimeSpan => jToken.ToObject(), + _ => jToken.ToArray() + }; + } + + return (JArray)jToken; + } + + /// + /// 获取字符串里的值的真正类型 + /// + /// + /// + public static string GetValueCSharpType(string input) + { + if (DateTime.TryParse(input, out _)) + return "DateTime"; + else if (int.TryParse(input, out _)) + return "int"; + else if (long.TryParse(input, out _)) + return "long"; + else if (decimal.TryParse(input, out _)) + return "decimal"; + else if (bool.TryParse(input, out _)) + return "bool"; + else + return "string"; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/IdentityService.cs b/Admin.NET/Admin.NET.Core/Service/APIJSON/IdentityService.cs new file mode 100644 index 00000000..f62bde57 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/IdentityService.cs @@ -0,0 +1,96 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Security.Claims; + +namespace Admin.NET.Core.Service; + +/// +/// 权限验证 +/// +public class IdentityService : ITransient +{ + private readonly IHttpContextAccessor _context; + private readonly List _roles; + + public IdentityService(IHttpContextAccessor context, IOptions roles) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _roles = roles.Value.Roles; + } + + /// + /// 获取当前用户Id + /// + /// + public string GetUserIdentity() + { + return _context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + } + + /// + /// 获取当前用户权限名称 + /// + /// + public string GetUserRoleName() + { + return _context.HttpContext.User.FindFirstValue(ClaimTypes.Role); + } + + /// + /// 获取当前用户权限 + /// + /// + public APIJSON_Role GetRole() + { + var role = string.IsNullOrEmpty(GetUserRoleName()) + ? _roles.FirstOrDefault() + : _roles.FirstOrDefault(it => it.RoleName.Equals(GetUserRoleName(), StringComparison.CurrentCultureIgnoreCase)); + return role; + } + + /// + /// 获取当前表的可查询字段 + /// + /// + /// + public (bool, string) GetSelectRole(string table) + { + var role = GetRole(); + if (role == null || role.Select == null || role.Select.Table == null) + return (false, $"appsettings.json权限配置不正确!"); + + var tablerole = role.Select.Table.FirstOrDefault(it => it == "*" || it.Equals(table, StringComparison.CurrentCultureIgnoreCase)); + if (string.IsNullOrEmpty(tablerole)) + return (false, $"表名{table}没权限查询!"); + + var index = Array.IndexOf(role.Select.Table, tablerole); + var selectrole = role.Select.Column[index]; + return (true, selectrole); + } + + /// + /// 当前列是否在角色里面 + /// + /// + /// + /// + public bool ColIsRole(string col, string[] selectrole) + { + if (selectrole.Contains("*")) return true; + + if (col.Contains('(') && col.Contains(')')) + { + var reg = new Regex(@"\(([^)]*)\)"); + var match = reg.Match(col); + return selectrole.Contains(match.Result("$1"), StringComparer.CurrentCultureIgnoreCase); + } + else + { + return selectrole.Contains(col, StringComparer.CurrentCultureIgnoreCase); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/SelectTable.cs b/Admin.NET/Admin.NET.Core/Service/APIJSON/SelectTable.cs new file mode 100644 index 00000000..9b6f0ecd --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/SelectTable.cs @@ -0,0 +1,974 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using AspectCore.Extensions.Reflection; +using System.Dynamic; + +namespace Admin.NET.Core.Service; + +/// +/// +/// +public class SelectTable : ISingleton +{ + private readonly IdentityService _identitySvc; + private readonly TableMapper _tableMapper; + private readonly ISqlSugarClient _db; + + public SelectTable(IdentityService identityService, TableMapper tableMapper, ISqlSugarClient dbClient) + { + _identitySvc = identityService; + _tableMapper = tableMapper; + _db = dbClient; + } + + /// + /// 判断表名是否正确,若不正确则抛异常 + /// + /// + /// + public virtual bool IsTable(string table) + { + return _db.DbMaintenance.GetTableInfoList().Any(it => it.Name.Equals(table, StringComparison.CurrentCultureIgnoreCase)) + ? true + : throw new Exception($"表名【{table}】不正确!"); + } + + /// + /// 判断表的列名是否正确,如果不正确则抛异常,更早地暴露给调用方 + /// + /// + /// + /// + public virtual bool IsCol(string table, string col) + { + return _db.DbMaintenance.GetColumnInfosByTableName(table).Any(it => it.DbColumnName.Equals(col, StringComparison.CurrentCultureIgnoreCase)) + ? true + : throw new Exception($"表【{table}】不存在列【{col}】!请检查输入参数"); + } + + /// + /// 查询列表数据 + /// + /// + /// + /// + /// + /// + /// + /// + public virtual Tuple GetTableData(string subtable, int page, int count, int query, string json, JObject dd) + { + var role = _identitySvc.GetSelectRole(subtable); + if (!role.Item1) + throw new Exception(role.Item2); + + var selectrole = role.Item2; + subtable = _tableMapper.GetTableName(subtable); + + var values = JObject.Parse(json); + page = values["page"] == null ? page : int.Parse(values["page"].ToString()); + count = values["count"] == null ? count : int.Parse(values["count"].ToString()); + query = values["query"] == null ? query : int.Parse(values["query"].ToString()); + values.Remove("page"); + values.Remove("count"); + // 构造查询过程 + var tb = SugarQueryable(subtable, selectrole, values, dd); + + // 实际会在这里执行 + if (query == 1) // 1-总数 + { + return new Tuple(null, tb.MergeTable().Count()); + } + else + { + if (page > 0) // 分页 + { + int total = 0; + if (query == 0) + return new Tuple(tb.ToPageList(page, count), total); // 0-对象 + else + return new Tuple(tb.ToPageList(page, count, ref total), total); // 2-以上全部 + } + else // 列表 + { + IList l = tb.ToList(); + return query == 0 ? new Tuple(l, 0) : new Tuple(l, l.Count); + } + } + } + + /// + /// 解析并查询 + /// + /// + /// + public virtual JObject Query(string queryJson) + { + var queryJobj = JObject.Parse(queryJson); + return Query(queryJobj); + } + + /// + /// 单表查询 + /// + /// + /// 返回数据的节点名称 默认为 infos + /// + public virtual JObject QuerySingle(JObject queryObj, string nodeName = "infos") + { + var resultObj = new JObject(); + + var total = 0; + foreach (var item in queryObj) + { + var key = item.Key.Trim(); + if (key.EndsWith("[]")) + { + total = QuerySingleList(resultObj, item, nodeName); + } + else if (key.Equals("func")) + { + ExecFunc(resultObj, item); + } + else if (key.Equals("total@") || key.Equals("total")) + { + resultObj.Add("total", total); + } + } + return resultObj; + } + + /// + /// 获取查询语句 + /// + /// + /// + public virtual string ToSql(JObject queryObj) + { + foreach (var item in queryObj) + { + if (item.Key.Trim().EndsWith("[]")) + return ToSql(item); + } + return string.Empty; + } + + /// + /// 解析并查询 + /// + /// + /// + public virtual JObject Query(JObject queryObj) + { + var resultObj = new JObject(); + + int total; + foreach (var item in queryObj) + { + var key = item.Key.Trim(); + if (key.Equals("[]")) // 列表 + { + total = QueryMoreList(resultObj, item); + resultObj.Add("total", total); // 只要是列表查询都自动返回总数 + } + else if (key.EndsWith("[]")) + { + total = QuerySingleList(resultObj, item); + } + else if (key.Equals("func")) + { + ExecFunc(resultObj, item); + } + else if (key.Equals("total@") || key.Equals("total")) + { + // resultObj.Add("total", total); + continue; + } + else // 单条 + { + var template = GetFirstData(key, item.Value.ToString(), resultObj); + if (template != null) + resultObj.Add(key, JToken.FromObject(template)); + } + } + return resultObj; + } + + // 动态调用方法 + private static object ExecFunc(string funcname, object[] param, Type[] types) + { + var method = typeof(FuncList).GetMethod(funcname); + var reflector = method.GetReflector(); + var result = reflector.Invoke(new FuncList(), param); + return result; + } + + // 生成sql + private string ToSql(string subtable, int page, int count, int query, string json) + { + var values = JObject.Parse(json); + page = values["page"] == null ? page : int.Parse(values["page"].ToString()); + count = values["count"] == null ? count : int.Parse(values["count"].ToString()); + _ = values["query"] == null ? query : int.Parse(values["query"].ToString()); + values.Remove("page"); + values.Remove("count"); + subtable = _tableMapper.GetTableName(subtable); + var tb = SugarQueryable(subtable, "*", values, null); + var sqlObj = tb.Skip((page - 1) * count).Take(10).ToSql(); + return sqlObj.Key; + } + + /// + /// 查询第一条数据 + /// + /// + /// + /// + /// + /// + private dynamic GetFirstData(string subtable, string json, JObject job) + { + var role = _identitySvc.GetSelectRole(subtable); + if (!role.Item1) + throw new Exception(role.Item2); + + var selectrole = role.Item2; + subtable = _tableMapper.GetTableName(subtable); + + var values = JObject.Parse(json); + values.Remove("page"); + values.Remove("count"); + var tb = SugarQueryable(subtable, selectrole, values, job).First(); + var dic = (IDictionary)tb; + foreach (var item in values.Properties().Where(it => it.Name.EndsWith("()"))) + { + if (item.Value.IsNullOrEmpty()) + { + var func = item.Value.ToString().Substring(0, item.Value.ToString().IndexOf("(")); + var param = item.Value.ToString().Substring(item.Value.ToString().IndexOf("(") + 1).TrimEnd(')'); + var types = new List(); + var paramss = new List(); + foreach (var va in param.Split(',')) + { + types.Add(typeof(object)); + paramss.Add(tb.Where(it => it.Key.Equals(va)).Select(i => i.Value)); + } + dic[item.Name] = ExecFunc(func, paramss.ToArray(), types.ToArray()); + } + } + return tb; + } + + // 单表查询,返回的数据在指定的NodeName节点 + private int QuerySingleList(JObject resultObj, KeyValuePair item, string nodeName) + { + var key = item.Key.Trim(); + var jb = JObject.Parse(item.Value.ToString()); + int page = jb["page"] == null ? 0 : int.Parse(jb["page"].ToString()); + int count = jb["count"] == null ? 10 : int.Parse(jb["count"].ToString()); + int query = jb["query"] == null ? 2 : int.Parse(jb["query"].ToString()); // 默认输出数据和数量 + int total = 0; + + jb.Remove("page"); jb.Remove("count"); jb.Remove("query"); + + var htt = new JArray(); + foreach (var t in jb) + { + var datas = GetTableData(t.Key, page, count, query, t.Value.ToString(), null); + if (query > 0) + total = datas.Item2; + + foreach (var data in datas.Item1) + { + htt.Add(JToken.FromObject(data)); + } + } + + if (!string.IsNullOrEmpty(nodeName)) + resultObj.Add(nodeName, htt); + else + resultObj.Add(key, htt); + + return total; + } + + // 生成sql + private string ToSql(KeyValuePair item) + { + var jb = JObject.Parse(item.Value.ToString()); + int page = jb["page"] == null ? 0 : int.Parse(jb["page"].ToString()); + int count = jb["count"] == null ? 10 : int.Parse(jb["count"].ToString()); + int query = jb["query"] == null ? 2 : int.Parse(jb["query"].ToString()); // 默认输出数据和数量 + + jb.Remove("page"); jb.Remove("count"); jb.Remove("query"); + foreach (var t in jb) + { + return ToSql(t.Key, page, count, query, t.Value.ToString()); + } + return string.Empty; + } + + // 单表查询 + private int QuerySingleList(JObject resultObj, KeyValuePair item) + { + var key = item.Key.TrimEnd("[]"); + return QuerySingleList(resultObj, item, key); + } + + /// + /// 多列表查询 + /// + /// + /// + /// + private int QueryMoreList(JObject resultObj, KeyValuePair item) + { + int total = 0; + + var jb = JObject.Parse(item.Value.ToString()); + var page = jb["page"] == null ? 0 : int.Parse(jb["page"].ToString()); + var count = jb["count"] == null ? 10 : int.Parse(jb["count"].ToString()); + var query = jb["query"] == null ? 2 : int.Parse(jb["query"].ToString()); // 默认输出数据和数量 + jb.Remove("page"); jb.Remove("count"); jb.Remove("query"); + var htt = new JArray(); + List tables = new List(), where = new List(); + foreach (var t in jb) + { + tables.Add(t.Key); where.Add(t.Value.ToString()); + } + if (tables.Count > 0) + { + string table = tables[0].TrimEnd("[]"); + var temp = GetTableData(table, page, count, query, where[0], null); + if (query > 0) + total = temp.Item2; + + // 关联查询,先查子表数据,再根据外键循环查询主表 + foreach (var dd in temp.Item1) + { + var zht = new JObject + { + { table, JToken.FromObject(dd) } + }; + for (int i = 1; i < tables.Count; i++) // 从第二个表开始循环 + { + string subtable = tables[i]; + // 有bug,暂不支持[]分支 + //if (subtable.EndsWith("[]")) + //{ + // string tableName = subtable.TrimEnd("[]".ToCharArray()); + // var jbb = JObject.Parse(where[i]); + // page = jbb["page"] == null ? 0 : int.Parse(jbb["page"].ToString()); + // count = jbb["count"] == null ? 0 : int.Parse(jbb["count"].ToString()); + + // var lt = new JArray(); + // foreach (var d in GetTableData(tableName, page, count, query, item.Value[subtable].ToString(), zht).Item1) + // { + // lt.Add(JToken.FromObject(d)); + // } + // zht.Add(tables[i], lt); + //} + //else + //{ + var ddf = GetFirstData(subtable, where[i].ToString(), zht); + if (ddf != null) + zht.Add(subtable, JToken.FromObject(ddf)); + } + htt.Add(zht); + } + } + if (query != 1) + resultObj.Add("[]", htt); + + // 分页自动添加当前页数和数量 + if (page > 0 && count > 0) + { + resultObj.Add("page", page); + resultObj.Add("count", count); + resultObj.Add("max", (int)Math.Ceiling((decimal)total / count)); + } + + return total; + } + + // 执行方法 + private void ExecFunc(JObject resultObj, KeyValuePair item) + { + var jb = JObject.Parse(item.Value.ToString()); + + var dataJObj = new JObject(); + foreach (var f in jb) + { + var types = new List(); + var param = new List(); + foreach (var va in JArray.Parse(f.Value.ToString())) + { + types.Add(typeof(object)); + param.Add(va); + } + dataJObj.Add(f.Key, JToken.FromObject(ExecFunc(f.Key, param.ToArray(), types.ToArray()))); + } + resultObj.Add("func", dataJObj); + } + + /// + /// 构造查询过程 + /// + /// + /// + /// + /// + /// + private ISugarQueryable SugarQueryable(string subtable, string selectrole, JObject values, JObject dd) + { + IsTable(subtable); + + var tb = _db.Queryable(subtable, "tb"); + + // select + if (!values["@column"].IsNullOrEmpty()) + { + ProcessColumn(subtable, selectrole, values, tb); + } + else + { + tb.Select(selectrole); + } + + // 前几行 + ProcessLimit(values, tb); + + // where + ProcessWhere(subtable, values, tb, dd); + + // 排序 + ProcessOrder(subtable, values, tb); + + // 分组 + PrccessGroup(subtable, values, tb); + + // Having + ProcessHaving(values, tb); + + return tb; + } + + // 处理字段重命名 "@column":"toId:parentId",对应SQL是toId AS parentId,将查询的字段toId变为parentId返回 + private void ProcessColumn(string subtable, string selectrole, JObject values, ISugarQueryable tb) + { + var str = new System.Text.StringBuilder(100); + foreach (var item in values["@column"].ToString().Split(',')) + { + var ziduan = item.Split(':'); + var colName = ziduan[0]; + var ma = new Regex(@"\((\w+)\)").Match(colName); + // 处理max、min这样的函数 + if (ma.Success && ma.Groups.Count > 1) + colName = ma.Groups[1].Value; + + // 判断列表是否有权限 sum(1)、sum(*)、Count(1)这样的值直接有效 + if (colName == "*" || int.TryParse(colName, out int colNumber) || (IsCol(subtable, colName) && _identitySvc.ColIsRole(colName, selectrole.Split(',')))) + { + if (ziduan.Length > 1) + { + if (ziduan[1].Length > 20) + throw new Exception("别名不能超过20个字符"); + + str.Append(ziduan[0] + " as `" + ReplaceSQLChar(ziduan[1]) + "`,"); + } + // 不对函数加``,解决sum(*)、Count(1)等不能使用的问题 + else if (ziduan[0].Contains('(')) + { + str.Append(ziduan[0] + ","); + } + else + str.Append("`" + ziduan[0] + "`" + ","); + } + } + if (string.IsNullOrEmpty(str.ToString())) + throw new Exception($"表名{subtable}没有可查询的字段!"); + + tb.Select(str.ToString().TrimEnd(',')); + } + + /// + /// 构造查询条件 where + /// + /// + /// + /// + /// + private void ProcessWhere(string subtable, JObject values, ISugarQueryable tb, JObject dd) + { + var conModels = new List(); + if (!values["identity"].IsNullOrEmpty()) + conModels.Add(new ConditionalModel() { FieldName = values["identity"].ToString(), ConditionalType = ConditionalType.Equal, FieldValue = _identitySvc.GetUserIdentity() }); + + foreach (var va in values) + { + string key = va.Key.Trim(); + string fieldValue = va.Value.ToString(); + if (key.StartsWith("@")) + { + continue; + } + if (key.EndsWith("$")) // 模糊查询 + { + FuzzyQuery(subtable, conModels, va); + } + else if (key.EndsWith("{}")) // 逻辑运算 + { + ConditionQuery(subtable, conModels, va); + } + else if (key.EndsWith("%")) // bwtween查询 + { + ConditionBetween(subtable, conModels, va, tb); + } + else if (key.EndsWith("@")) // 关联上一个table + { + if (dd == null) + continue; + + var str = fieldValue.Split('/'); + var lastTableRecord = ((JObject)dd[str[^2]]); + if (!lastTableRecord.ContainsKey(str[^1])) + throw new Exception($"找不到关联列:{str},请在{str[^2]}@column中设置"); + + var value = lastTableRecord[str[^1]].ToString(); + conModels.Add(new ConditionalModel() { FieldName = key.TrimEnd('@'), ConditionalType = ConditionalType.Equal, FieldValue = value }); + } + else if (key.EndsWith("~")) // 不等于(应该是正则匹配) + { + //conModels.Add(new ConditionalModel() { FieldName = key.TrimEnd('~'), ConditionalType = ConditionalType.NoEqual, FieldValue = fieldValue }); + } + else if (IsCol(subtable, key.TrimEnd('!'))) // 其他where条件 + { + ConditionEqual(subtable, conModels, va); + } + } + if (conModels.Any()) + tb.Where(conModels); + } + + // "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...", + // SQL函数条件,一般和 @group一起用,函数一般在 @column里声明 + private static void ProcessHaving(JObject values, ISugarQueryable tb) + { + if (!values["@having"].IsNullOrEmpty()) + { + var hw = new List(); + var havingItems = new List(); + if (values["@having"].HasValues) + { + havingItems = values["@having"].Select(p => p.ToString()).ToList(); + } + else + { + havingItems.Add(values["@having"].ToString()); + } + foreach (var item in havingItems) + { + var and = item.ToString(); + var model = new ConditionalModel(); + if (and.Contains(">=")) + { + model.FieldName = and.Split(new string[] { ">=" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.GreaterThanOrEqual; + model.FieldValue = and.Split(new string[] { ">=" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + else if (and.Contains("<=")) + { + model.FieldName = and.Split(new string[] { "<=" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.LessThanOrEqual; + model.FieldValue = and.Split(new string[] { "<=" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + else if (and.Contains('>')) + { + model.FieldName = and.Split(new string[] { ">" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.GreaterThan; + model.FieldValue = and.Split(new string[] { ">" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + else if (and.Contains('<')) + { + model.FieldName = and.Split(new string[] { "<" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.LessThan; + model.FieldValue = and.Split(new string[] { "<" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + else if (and.Contains("!=")) + { + model.FieldName = and.Split(new string[] { "!=" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.NoEqual; + model.FieldValue = and.Split(new string[] { "!=" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + else if (and.Contains('=')) + { + model.FieldName = and.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries)[0]; + model.ConditionalType = ConditionalType.Equal; + model.FieldValue = and.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries)[1]; + } + hw.Add(model); + } + //var d = db.Context.Utilities.ConditionalModelToSql(hw); + //tb.Having(d.Key, d.Value); + tb.Having(string.Join(",", havingItems)); + } + } + + // "@group":"column0,column1...",分组方式。如果 @column里声明了Table的id,则id也必须在 @group中声明;其它情况下必须满足至少一个条件: + // 1.分组的key在 @column里声明 + // 2.Table主键在 @group中声明 + private void PrccessGroup(string subtable, JObject values, ISugarQueryable tb) + { + if (!values["@group"].IsNullOrEmpty()) + { + var groupList = new List(); // 多库兼容写法 + foreach (var col in values["@group"].ToString().Split(',')) + { + if (IsCol(subtable, col)) + { + // str.Append(and + ","); + groupList.Add(new GroupByModel() { FieldName = col }); + } + } + if (groupList.Any()) + tb.GroupBy(groupList); + } + } + + // 处理排序 "@order":"name-,id"查询按 name降序、id默认顺序 排序的User数组 + private void ProcessOrder(string subtable, JObject values, ISugarQueryable tb) + { + if (!values["@order"].IsNullOrEmpty()) + { + var orderList = new List(); // 多库兼容写法 + foreach (var item in values["@order"].ToString().Split(',')) + { + string col = item.Replace("-", "").Replace("+", "").Replace(" desc", "").Replace(" asc", ""); // 增加对原生排序的支持 + if (IsCol(subtable, col)) + { + orderList.Add(new OrderByModel() + { + FieldName = col, + OrderByType = item.EndsWith("-") || item.EndsWith(" desc") ? OrderByType.Desc : OrderByType.Asc + }); + } + } + + if (orderList.Any()) + tb.OrderBy(orderList); + } + } + + /// + /// 表内参数"@count"(int):查询前几行,不能同时使用count和@count函数 + /// + /// + /// + private static void ProcessLimit(JObject values, ISugarQueryable tb) + { + if (!values["@count"].IsNullOrEmpty()) + { + int c = values["@count"].ToObject(); + tb.Take(c); + } + } + + // 条件查询 "key{}":"条件0,条件1...",条件为任意SQL比较表达式字符串,非Number类型必须用''包含条件的值,如'a' + // &, |, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。 + // 横或纵与:同一字段的值内条件默认 | 或连接,不同字段的条件默认 & 与连接。 + // ① & 可用于"key&{}":"条件"等 + // ② | 可用于"key|{}":"条件", "key|{}":[] 等,一般可省略 + // ③ ! 可单独使用,如"key!":Object,也可像&,|一样配合其他功能符使用 + private void ConditionQuery(string subtable, List conModels, KeyValuePair va) + { + var vakey = va.Key.Trim(); + var field = vakey.TrimEnd("{}".ToCharArray()); + var columnName = field.TrimEnd(new char[] { '&', '|' }); + IsCol(subtable, columnName); + var ddt = new List>(); + foreach (var and in va.Value.ToString().Split(',')) + { + var model = new ConditionalModel + { + FieldName = columnName + }; + + if (and.StartsWith(">=")) + { + model.ConditionalType = ConditionalType.GreaterThanOrEqual; + model.FieldValue = and.TrimStart(">=".ToCharArray()); + } + else if (and.StartsWith("<=")) + { + model.ConditionalType = ConditionalType.LessThanOrEqual; + model.FieldValue = and.TrimStart("<=".ToCharArray()); + } + else if (and.StartsWith(">")) + { + model.ConditionalType = ConditionalType.GreaterThan; + model.FieldValue = and.TrimStart('>'); + } + else if (and.StartsWith("<")) + { + model.ConditionalType = ConditionalType.LessThan; + model.FieldValue = and.TrimStart('<'); + } + model.CSharpTypeName = FuncList.GetValueCSharpType(model.FieldValue); + ddt.Add(new KeyValuePair(field.EndsWith("!") ? WhereType.Or : WhereType.And, model)); + } + conModels.Add(new ConditionalCollections() { ConditionalList = ddt }); + } + + /// + /// "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 + /// 目前不支持数组形式 + /// + /// + /// + /// + /// + private static void ConditionBetween(string subtable, List conModels, KeyValuePair va, ISugarQueryable tb) + { + var vakey = va.Key.Trim(); + var field = vakey.TrimEnd("%".ToCharArray()); + var inValues = new List(); + if (va.Value.HasValues) + { + foreach (var cm in va.Value) + { + inValues.Add(cm.ToString()); + } + } + else + { + inValues.Add(va.Value.ToString()); + } + + for (var i = 0; i < inValues.Count; i++) + { + var fileds = inValues[i].Split(','); + if (fileds.Length == 2) + { + var type = FuncList.GetValueCSharpType(fileds[0]); + ObjectFuncModel f = ObjectFuncModel.Create("between", field, $"{{{type}}}:{fileds[0]}", $"{{{type}}}:{fileds[1]}"); + tb.Where(f); + } + } + } + + /// + /// 等于、不等于、in 、not in + /// + /// + /// + /// + private void ConditionEqual(string subtable, List conModels, KeyValuePair va) + { + var key = va.Key; + var fieldValue = va.Value.ToString(); + // in / not in + if (va.Value is JArray) + { + conModels.Add(new ConditionalModel() + { + FieldName = key.TrimEnd('!'), + ConditionalType = key.EndsWith("!") ? ConditionalType.NotIn : ConditionalType.In, + FieldValue = va.Value.ToObject().Aggregate((a, b) => a + "," + b) + }); + } + else + { + if (string.IsNullOrEmpty(fieldValue)) + { + // is not null or '' + if (key.EndsWith("!")) + { + conModels.Add(new ConditionalModel() { FieldName = key.TrimEnd('!'), ConditionalType = ConditionalType.IsNot, FieldValue = null }); + conModels.Add(new ConditionalModel() { FieldName = key.TrimEnd('!'), ConditionalType = ConditionalType.IsNot, FieldValue = "" }); + } + //is null or '' + else + { + conModels.Add(new ConditionalModel() { FieldName = key.TrimEnd('!'), FieldValue = null }); + } + } + // = / != + else + { + conModels.Add(new ConditionalModel() + { + FieldName = key.TrimEnd('!'), + ConditionalType = key.EndsWith("!") ? ConditionalType.NoEqual : ConditionalType.Equal, + FieldValue = fieldValue + }); + } + } + } + + // 模糊搜索 "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 + private void FuzzyQuery(string subtable, List conModels, KeyValuePair va) + { + var vakey = va.Key.Trim(); + var fieldValue = va.Value.ToString(); + var conditionalType = ConditionalType.Like; + if (IsCol(subtable, vakey.TrimEnd('$'))) + { + // 支持三种like查询 + if (fieldValue.StartsWith("%") && fieldValue.EndsWith("%")) + { + conditionalType = ConditionalType.Like; + } + else if (fieldValue.StartsWith("%")) + { + conditionalType = ConditionalType.LikeRight; + } + else if (fieldValue.EndsWith("%")) + { + conditionalType = ConditionalType.LikeLeft; + } + conModels.Add(new ConditionalModel() { FieldName = vakey.TrimEnd('$'), ConditionalType = conditionalType, FieldValue = fieldValue.TrimEnd("%".ToArray()).TrimStart("%".ToArray()) }); + } + } + + // 处理sql注入 + private string ReplaceSQLChar(string str) + { + if (string.IsNullOrWhiteSpace(str)) + return string.Empty; + + str = str.Replace("'", ""); + str = str.Replace(";", ""); + str = str.Replace(",", ""); + str = str.Replace("?", ""); + str = str.Replace("<", ""); + str = str.Replace(">", ""); + str = str.Replace("(", ""); + str = str.Replace(")", ""); + str = str.Replace("@", ""); + str = str.Replace("=", ""); + str = str.Replace("+", ""); + str = str.Replace("*", ""); + str = str.Replace("&", ""); + str = str.Replace("#", ""); + str = str.Replace("%", ""); + str = str.Replace("$", ""); + str = str.Replace("\"", ""); + + // 删除与数据库相关的词 + str = Regex.Replace(str, "delete from", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "drop table", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "truncate", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "xp_cmdshell", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "exec master", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "net localgroup administrators", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "net user", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "-", "", RegexOptions.IgnoreCase); + str = Regex.Replace(str, "truncate", "", RegexOptions.IgnoreCase); + return str; + } + + /// + /// 单条插入 + /// + /// + /// + /// + /// (各种类型的)id + public object InsertSingle(string tableName, JObject cols, APIJSON_Role role = null) + { + role ??= _identitySvc.GetRole(); + var dt = new Dictionary(); + + foreach (var f in cols) // 遍历字段 + { + if (//f.Key.ToLower() != "id" && //是否一定要传id + IsCol(tableName, f.Key) && + (role.Insert.Column.Contains("*") || role.Insert.Column.Contains(f.Key, StringComparer.CurrentCultureIgnoreCase))) + dt.Add(f.Key, FuncList.TransJObjectToSugarPara(f.Value)); + } + // 如果外部没传Id,就后端生成或使用数据库默认值,如果都没有会出错 + object id; + if (!dt.ContainsKey("id")) + { + id = YitIdHelper.NextId();//自己生成id的方法,可以由外部传入 + dt.Add("id", id); + } + else + { + id = dt["id"]; + } + _db.Insertable(dt).AS(tableName).ExecuteCommand();//根据主键类型设置返回雪花或自增,目前返回条数 + + return id; + } + + /// + /// 为每天记录创建udpate sql + /// + /// + /// + /// + /// + public int UpdateSingleRecord(string tableName, JObject record, APIJSON_Role role = null) + { + role ??= _identitySvc.GetRole(); + if (!record.ContainsKey("id")) + throw Oops.Bah("未传主键id"); + + var dt = new Dictionary(); + var sb = new StringBuilder(100); + object id = null; + foreach (var f in record)//遍历每个字段 + { + if (f.Key.Equals("id", StringComparison.OrdinalIgnoreCase)) + { + if (f.Value is JArray) + { + sb.Append($"{f.Key} in (@{f.Key})"); + id = FuncList.TransJArrayToSugarPara(f.Value); + } + else + { + sb.Append($"{f.Key}=@{f.Key}"); + id = FuncList.TransJObjectToSugarPara(f.Value); + } + } + else if (IsCol(tableName, f.Key) && (role.Update.Column.Contains("*") || role.Update.Column.Contains(f.Key, StringComparer.CurrentCultureIgnoreCase))) + { + dt.Add(f.Key, FuncList.TransJObjectToSugarPara(f.Value)); + } + } + string whereSql = sb.ToString(); + int count = _db.Updateable(dt).AS(tableName).Where(whereSql, new { id }).ExecuteCommand(); + return count; + } + + /// + /// 更新单表,支持同表多条记录 + /// + /// + /// + /// + /// + public int UpdateSingleTable(string tableName, JToken records, APIJSON_Role role = null) + { + role ??= _identitySvc.GetRole(); + int count = 0; + if (records is JArray) + { + foreach (var record in records.ToObject()) + { + count += UpdateSingleRecord(tableName, record, role); + } + } + else + { + count = UpdateSingleRecord(tableName, records.ToObject(), role); + } + return count; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/TableMapper.cs b/Admin.NET/Admin.NET.Core/Service/APIJSON/TableMapper.cs new file mode 100644 index 00000000..1cbb5ff4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/TableMapper.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 表名映射 +/// +public class TableMapper : ITransient +{ + private readonly Dictionary _options = new(StringComparer.OrdinalIgnoreCase); + + public TableMapper(IOptions> options) + { + foreach (var item in options.Value) + { + _options.Add(item.Key, item.Value); + } + } + + /// + /// 获取表别名 + /// + /// + /// + public string GetTableName(string oldname) + { + return _options.ContainsKey(oldname) ? _options[oldname] : oldname; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/APIJSON/用例APIFOX.json b/Admin.NET/Admin.NET.Core/Service/APIJSON/用例APIFOX.json new file mode 100644 index 00000000..b000e180 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/APIJSON/用例APIFOX.json @@ -0,0 +1 @@ +{"apifoxProject":"1.0.0","$schema":{"app":"apifox","type":"project","version":"1.2.0"},"info":{"name":"Admin.Net","description":"","mockRule":{"rules":[],"enableSystemRule":true}},"apiCollection":[{"name":"根目录","id":29711526,"auth":{},"parentId":0,"serverId":"","description":"","identityPattern":{"httpApi":{"type":"methodAndPath","bodyType":""}},"preProcessors":[{"id":"inheritProcessors","type":"inheritProcessors","data":{}}],"postProcessors":[{"id":"inheritProcessors","type":"inheritProcessors","data":{}}],"inheritPostProcessors":{},"inheritPreProcessors":{},"items":[{"name":"aPIJSON","id":29711668,"auth":{},"parentId":0,"serverId":"","description":"说明文档:https://github.com/Tencent/APIJSON/blob/master/Document.md#3.1","identityPattern":{"httpApi":{"type":"inherit","bodyType":""}},"preProcessors":[{"id":"inheritProcessors","type":"inheritProcessors","data":{}}],"postProcessors":[{"id":"inheritProcessors","type":"inheritProcessors","data":{}}],"inheritPostProcessors":{},"inheritPreProcessors":{},"items":[{"name":"统一查询入口","api":{"id":"151219333","method":"post","path":"/api/aPIJSON/get","parameters":{"query":[],"path":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[]},"responses":[{"id":"405647728","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{"code":{"type":"integer"},"type":{"type":"string"},"message":{"type":"string"},"result":{"type":"object","properties":{"[]":{"type":"array","items":{"type":"object","properties":{},"x-apifox-orders":[]}},"page":{"type":"integer","description":"当前页码"},"count":{"type":"integer","description":"每页条数"},"max":{"type":"integer","description":"最大页数"},"total":{"type":"integer","description":"总条数"}},"x-apifox-orders":["[]","page","count","max","total"]},"extras":{"type":"null"},"time":{"type":"string"}},"required":["code","type","message","result","extras","time"],"x-apifox-orders":["code","type","message","result","extras","time"]}}],"responseExamples":[],"requestBody":{"type":"application/json","parameters":[],"jsonSchema":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275307"},"x-apifox-orders":[],"properties":{}},"example":"{\r\n \"table1\": {\r\n \"@column\": \"id\",\r\n \"httpmethod\": \"Get\",\r\n }\r\n}"},"description":"参数:{\"[]\":{\"SYS_LOG_OP\":{}}}","tags":["aPIJSON"],"status":"released","serverId":"","operationId":"api-aPIJSON-Post","sourceUrl":"","ordering":0,"cases":[{"id":143284761,"type":"http","path":null,"name":"单条查询","responseId":405647728,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table1\": {\r\n \"@column\": \"id,createtime,Actionname,loglevel,httpmethod,RequestParam\",//显示列\r\n //\"Actionname\":\"SwaggerCheckUrl\",\r\n //\"loglevel\":2,\r\n //\"httpmethod!\":[\"POST\",\"GET\"],//! not in\r\n \"createtime{}\": \">=2024-3-1\", //逻辑运算\r\n //\"isdelete\":0, //bool 支持 1、0、true、false \r\n \"RequestParam!\": null //not null\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.decba6c4-b3e2-40af-a30e-8c8d09865bf1\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"decba6c4-b3e2-40af-a30e-8c8d09865bf1\",\"requestIndex\":0,\"httpRequestId\":\"7e107ae6-74d4-44c8-8582-e9bfefb5e611\"},\"type\":\"http\",\"response\":{\"id\":\"7b5fc148-4f73-4e56-b054-26bebe9db57a\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Wed, 13 Mar 2024 08:19:05 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86375\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-14T07:18:51.6721222Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,57,54,51,53,53,48,48,50,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,48,58,51,51,58,50,51,46,51,56,55,53,57,48,51,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,76,111,103,76,101,118,101,108,34,58,50,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,51,44,92,34,99,111,117,110,116,92,34,58,50,44,92,34,113,117,101,114,121,92,34,58,51,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,51,32,49,54,58,49,57,58,48,54,34,125]},\"cookie\":[],\"responseTime\":99,\"responseSize\":328,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.698800027370453,\"wait\":0.3564000129699707,\"dns\":0,\"tcp\":0,\"firstByte\":96.46359997987747,\"download\":1.736199975013733,\"process\":0.03470003604888916,\"total\":101.28970003128052}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":157,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"table1\\\": {\\n \\\"@column\\\": \\\"id,createtime,Actionname,loglevel,httpmethod,RequestParam\\\",\\n \\\"createtime{}\\\": \\\">=2024-3-1\\\",\\n \\\"RequestParam!\\\": null\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"157\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Wed, 13 Mar 2024 08:19:05 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86375\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-14T07:18:51.6721222Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710317946491,\"requestStart\":1710317946493,\"offset\":{\"request\":2.698800027370453,\"socket\":3.0552000403404236,\"response\":99.5188000202179,\"end\":101.25499999523163,\"lookup\":3.0552000403404236,\"connect\":3.0552000403404236,\"done\":101.28970003128052}}}]}},\"responseValidation\":{\"schema\":{\"valid\":true,\"message\":\"\",\"errors\":null},\"responseCode\":{\"valid\":true}},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143284761,\"httpApiName\":\"统一入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"单条查询\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143272865,"type":"http","path":null,"name":"列表查询","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"[]\": {\r\n \"table1\": {\r\n \"@column\": \"id,createtime,httpmethod,RequestUrl,actionname,RequestParam,Elapsed\",//需要显示的列名\r\n //\"httpmethod\": \"POST\",//条件查询\r\n //\"RequestUrl$\": \"%swagger%\", //$模糊查询\r\n //\"@order\": \"RequestParam desc,createtime,actionname desc\", //按最新时间排序:-/desc 均为倒序 \r\n //\"@count\": \"10\", //前n条 很少用到 \r\n //\"RequestParam\":null,//匹配null or '',\r\n \"createtime%\":\"2024-3-5,2024-3-14\",// between 日期过滤\r\n \"Elapsed%\":\"1,20\"\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.1c48ba22-4372-4ab1-a874-a79449144e5d\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"1c48ba22-4372-4ab1-a874-a79449144e5d\",\"requestIndex\":0,\"httpRequestId\":\"70f19533-b333-460b-beeb-2e5ecbc5298c\"},\"type\":\"http\",\"response\":{\"id\":\"0f169b35-6729-4512-956d-2081d9fdb91d\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 14 Mar 2024 02:00:47 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-15T02:00:05.6689995Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,91,93,34,58,91,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,51,48,49,56,48,54,55,57,50,51,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,53,32,49,54,58,50,48,58,52,53,46,50,57,56,49,57,49,57,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,50,54,50,54,48,51,48,55,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,48,57,58,52,55,58,52,53,46,51,49,51,48,53,50,53,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,51,50,57,56,51,50,55,55,51,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,48,57,58,53,50,58,48,55,46,57,50,57,50,54,52,57,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,51,54,51,52,53,55,56,54,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,48,57,58,53,52,58,49,57,46,50,55,55,52,54,53,55,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,51,56,54,55,57,55,56,57,51,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,48,57,58,53,53,58,53,48,46,52,52,57,55,52,57,54,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,54,57,50,57,50,49,52,50,55,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,48,58,51,49,58,48,57,46,50,54,51,55,53,49,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,48,50,52,48,55,57,57,52,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,48,58,51,55,58,49,57,46,56,51,50,49,53,50,56,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,50,44,92,34,113,117,101,114,121,92,34,58,51,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,49,56,56,53,57,57,49,48,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,48,58,52,56,58,48,50,46,52,56,53,55,57,50,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,83,89,83,76,79,71,79,80,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,66,121,84,97,98,108,101,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,92,34,83,89,83,76,79,71,79,80,92,34,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,53,52,57,57,55,57,52,54,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,49,58,49,49,58,51,52,46,49,50,55,53,49,56,55,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,50,44,92,34,113,117,101,114,121,92,34,58,49,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,53,53,52,49,49,53,57,48,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,49,58,49,49,58,53,48,46,50,56,53,52,56,54,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,50,44,92,34,113,117,101,114,121,92,34,58,48,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,51,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,53,53,55,51,56,52,48,48,53,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,49,58,49,50,58,48,51,46,48,53,49,50,55,54,54,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,50,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,55,53,54,54,57,51,57,57,55,51,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,49,58,49,50,58,52,48,46,51,55,57,52,48,56,55,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,50,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,52,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,52,56,49,56,50,52,56,53,51,49,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,49,58,53,50,58,52,52,46,56,53,51,53,57,52,54,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,48,50,57,49,51,50,52,55,52,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,52,58,49,48,58,48,50,46,53,48,55,49,56,48,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,48,52,50,50,50,57,55,49,53,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,52,58,49,56,58,51,52,46,49,49,56,52,57,56,53,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,48,52,51,48,57,53,54,56,54,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,52,58,49,57,58,48,55,46,57,52,53,57,49,56,55,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,48,52,52,56,49,56,55,55,49,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,52,58,50,48,58,49,53,46,50,53,51,55,52,49,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,113,117,101,114,121,92,34,58,92,34,50,92,34,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,47,91,93,47,116,111,116,97,108,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,48,52,49,52,53,53,52,50,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,52,58,53,56,58,53,50,46,55,48,53,55,50,56,53,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,50,52,52,54,54,50,53,57,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,49,50,58,48,54,46,52,56,51,53,49,52,53,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,105,100,45,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,50,48,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,50,52,54,51,53,51,50,50,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,49,50,58,49,51,46,48,56,54,57,57,50,54,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,105,100,43,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,50,52,55,55,57,53,50,54,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,49,50,58,49,56,46,55,50,48,51,52,54,57,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,105,100,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,50,53,50,55,51,52,50,55,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,49,50,58,51,56,46,48,49,51,48,56,48,56,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,99,114,101,97,116,101,116,105,109,101,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,50,53,53,49,56,53,50,50,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,49,50,58,52,55,46,53,56,55,53,49,52,53,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,99,114,101,97,116,101,116,105,109,101,45,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,51,57,57,55,52,57,55,48,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,50,50,58,49,50,46,50,57,50,57,51,57,49,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,99,114,101,97,116,101,116,105,109,101,45,92,34,125,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,52,55,48,54,51,51,48,50,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,50,54,58,52,57,46,49,56,48,51,57,54,54,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,99,114,101,97,116,101,116,105,109,101,45,92,34,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,53,48,44,92,34,113,117,101,114,121,92,34,58,50,125,44,92,34,116,111,116,97,108,64,92,34,58,92,34,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,53,49,52,50,48,56,48,54,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,50,57,58,51,57,46,51,57,53,52,54,52,56,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,53,49,57,52,57,53,50,51,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,51,48,58,48,48,46,48,52,56,49,51,52,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,91,93,92,34,58,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,64,111,114,100,101,114,92,34,58,92,34,99,114,101,97,116,101,116,105,109,101,45,92,34,125,44,92,34,112,97,103,101,92,34,58,49,44,92,34,99,111,117,110,116,92,34,58,53,48,44,92,34,113,117,101,114,121,92,34,58,50,125,44,92,34,116,111,116,97,108,92,34,58,92,34,92,34,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,54,48,57,52,49,56,48,53,51,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,51,53,58,53,49,46,51,48,57,56,56,51,55,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,50,48,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,49,54,52,48,51,49,54,52,56,53,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,53,58,51,55,58,53,50,46,48,48,54,56,55,49,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,105,100,92,34,58,92,34,51,50,54,53,49,54,48,57,52,49,56,48,53,51,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,48,49,50,52,57,54,48,53,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,52,58,50,51,46,49,53,49,54,53,53,50,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,50,52,48,55,48,57,56,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,53,58,53,50,46,50,57,55,55,48,50,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,99,111,117,110,116,40,105,100,41,58,115,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,50,54,54,55,48,54,54,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,54,58,48,50,46,52,53,50,52,56,51,56,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,99,111,117,110,116,40,49,41,58,115,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,51,48,53,51,57,51,51,51,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,54,58,49,55,46,53,54,52,54,54,55,51,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,99,111,117,110,116,40,49,41,58,99,111,117,110,116,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,51,53,48,48,48,51,56,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,54,58,51,52,46,57,57,48,54,50,51,50,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,99,111,117,110,116,40,49,41,58,230,149,176,233,135,143,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,50,51,57,48,54,52,51,56,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,54,58,49,54,58,53,48,46,56,54,53,56,49,49,57,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,97,112,105,47,97,80,73,74,83,79,78,47,103,101,116,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,99,111,117,110,116,40,49,41,58,230,157,161,230,149,176,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,56,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,50,57,57,51,49,57,51,55,57,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,55,58,48,53,58,53,54,46,54,56,51,50,51,48,57,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,110,117,108,108,44,34,69,108,97,112,115,101,100,34,58,49,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,51,50,54,54,50,50,48,51,53,55,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,55,58,50,51,58,52,51,46,49,57,51,54,55,53,52,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,85,84,34,44,34,82,101,113,117,101,115,116,85,114,108,34,58,34,104,116,116,112,58,47,47,108,111,99,97,108,104,111,115,116,58,53,48,48,53,47,115,119,97,103,103,101,114,47,99,104,101,99,107,85,114,108,34,44,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,82,101,113,117,101,115,116,80,97,114,97,109,34,58,34,123,92,34,83,89,83,76,79,71,79,80,92,34,58,123,92,34,64,99,111,108,117,109,110,92,34,58,92,34,105,100,44,99,114,101,97,116,101,116,105,109,101,92,34,44,92,34,105,100,92,34,58,92,34,51,50,54,53,49,54,48,57,52,49,56,48,53,51,92,34,125,125,34,44,34,69,108,97,112,115,101,100,34,58,49,54,125,125,93,44,34,116,111,116,97,108,34,58,51,55,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,52,32,49,48,58,48,48,58,52,55,34,125]},\"cookie\":[],\"responseTime\":137,\"responseSize\":9324,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":1.5074999928474426,\"wait\":0.32520002126693726,\"dns\":0,\"tcp\":0,\"firstByte\":135.28540003299713,\"download\":1.2343999743461609,\"process\":0.05519998073577881,\"total\":138.40770000219345}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":195,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"[]\\\": {\\n \\\"table1\\\": {\\n \\\"@column\\\": \\\"id,createtime,httpmethod,RequestUrl,actionname,RequestParam,Elapsed\\\",\\n \\\"createtime%\\\": \\\"2024-3-5,2024-3-14\\\",\\n \\\"Elapsed%\\\": \\\"1,20\\\"\\n }\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"195\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 14 Mar 2024 02:00:47 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-15T02:00:05.6689995Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710381647691,\"requestStart\":1710381647692,\"offset\":{\"request\":1.5074999928474426,\"socket\":1.8327000141143799,\"response\":137.1181000471115,\"end\":138.35250002145767,\"lookup\":1.8327000141143799,\"connect\":1.8327000141143799,\"done\":138.40770000219345}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143272865,\"httpApiName\":\"统一入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"列表查询\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143191882,"type":"http","path":null,"name":"分页查询","responseId":405647728,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"[]\": {//列表\r\n \"SYSLOGOP\": {//表名\r\n \"@column\": \"id,createtime\",//只查询两列\r\n \"@order\":\"createtime-\" //-倒序排序\r\n },\r\n \"page\": 1, //分页查询:第1页\r\n \"count\": 3, //每页3条\r\n //\"query\": 1 //查询内容: 0-对象,1-总数,2-数据、总数,默认为2\r\n },\r\n //\"total\": \"\", //总数 默认返回总数,不用传\r\n \r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.bcfe838e-0335-42f2-ae40-05fb07285447\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"bcfe838e-0335-42f2-ae40-05fb07285447\",\"requestIndex\":0,\"httpRequestId\":\"74d59ab7-76bd-4c77-9bcf-714ff5eb470e\"},\"type\":\"http\",\"response\":{\"id\":\"673bc01a-a4df-4514-b2e9-b13172c5eca8\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Tue, 12 Mar 2024 08:29:32 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-13T08:27:50.0233019Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,91,93,34,58,91,123,34,83,89,83,76,79,71,79,80,34,58,123,34,73,100,34,58,51,50,55,56,53,49,52,51,50,52,49,53,52,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,50,32,49,54,58,50,57,58,50,55,46,56,48,55,57,53,49,52,34,125,125,44,123,34,83,89,83,76,79,71,79,80,34,58,123,34,73,100,34,58,51,50,55,56,53,49,49,56,51,48,53,56,54,49,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,50,32,49,54,58,50,55,58,53,48,46,52,48,50,49,56,34,125,125,44,123,34,83,89,83,76,79,71,79,80,34,58,123,34,73,100,34,58,51,50,55,56,53,49,49,53,55,51,54,51,56,57,44,34,67,114,101,97,116,101,84,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,50,32,49,54,58,50,55,58,52,48,46,51,54,53,57,51,48,54,34,125,125,93,44,34,112,97,103,101,34,58,49,44,34,99,111,117,110,116,34,58,51,44,34,109,97,120,34,58,49,50,49,44,34,116,111,116,97,108,34,58,51,54,49,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,50,32,49,54,58,50,57,58,51,51,34,125]},\"cookie\":[],\"responseTime\":72,\"responseSize\":376,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":1.7229999899864197,\"wait\":0.46160000562667847,\"dns\":0,\"tcp\":0,\"firstByte\":69.22839999198914,\"download\":1.886900007724762,\"process\":0.04899996519088745,\"total\":73.34889996051788}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":136,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"[]\\\": {\\n \\\"SYSLOGOP\\\": {\\n \\\"@column\\\": \\\"id,createtime\\\",\\n \\\"@order\\\": \\\"createtime-\\\"\\n },\\n \\\"page\\\": 1,\\n \\\"count\\\": 3\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"136\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Tue, 12 Mar 2024 08:29:32 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-13T08:27:50.0233019Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710232173277,\"requestStart\":1710232173279,\"offset\":{\"request\":1.7229999899864197,\"socket\":2.184599995613098,\"response\":71.41299998760223,\"end\":73.299899995327,\"lookup\":2.184599995613098,\"connect\":2.184599995613098,\"done\":73.34889996051788}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143191882,\"httpApiName\":\"统一入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"分页查询\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143312444,"type":"http","path":null,"name":"查询数量(聚合函数)","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"SYSLOGOP\": {\r\n \"@column\": \"count(1):数量,sum(id):合计\",//支持聚合函数count、sum等, :后跟别名\r\n \"httpmethod\":\"GET\"\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.8cdec2eb-2a6e-4b14-b841-d45980eb6969\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"8cdec2eb-2a6e-4b14-b841-d45980eb6969\",\"requestIndex\":0,\"httpRequestId\":\"b833e53d-ca56-464c-a0f3-bf293186f3c0\"},\"type\":\"http\",\"response\":{\"id\":\"c4a883ba-8b68-4bcf-b155-5300deb9b77e\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Tue, 12 Mar 2024 03:49:08 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86393\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-13T03:46:44.5267221Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,83,89,83,76,79,71,79,80,34,58,123,34,230,149,176,233,135,143,34,58,54,44,34,229,144,136,232,174,161,34,58,49,57,53,56,55,56,50,53,54,53,53,56,55,53,48,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,50,32,49,49,58,52,57,58,48,56,34,125]},\"cookie\":[],\"responseTime\":57,\"responseSize\":145,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.178399980068207,\"wait\":0.46469998359680176,\"dns\":0,\"tcp\":0,\"firstByte\":54.569000005722046,\"download\":1.8371000289916992,\"process\":0.08829998970031738,\"total\":59.13749998807907}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":96,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"SYSLOGOP\\\": {\\n \\\"@column\\\": \\\"count(1):数量,sum(id):合计\\\",\\n \\\"httpmethod\\\": \\\"GET\\\"\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"96\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Tue, 12 Mar 2024 03:49:08 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86393\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-13T03:46:44.5267221Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710215348896,\"requestStart\":1710215348898,\"offset\":{\"request\":2.178399980068207,\"socket\":2.6430999636650085,\"response\":57.212099969387054,\"end\":59.049199998378754,\"lookup\":2.6430999636650085,\"connect\":2.6430999636650085,\"done\":59.13749998807907}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143312444,\"httpApiName\":\"统一入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"查询数量(聚合函数)\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143871046,"type":"http","path":null,"name":"关联查询","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"//用于子表补充数据,没有实现join\r\n{\r\n \"[]\": {\r\n //必须先查子表,再循环补充主表数据\r\n \"table3\": {\r\n //\"@column\": \"id\",//必须在此返回后面的关联列\r\n \"text\": \"update3\",\r\n \"@order\": \"id-\", //只能在第一个表排序\r\n },\r\n \"table2\": {\r\n \"id@\": \"/table3/table2id\",\r\n //\"@order\": \"id-\", //这里排序无效\r\n },\r\n \"table1\": {\r\n \"@column\": \"id,httpmethod\",\r\n \"id@\": \"/table2/table1id\",\r\n },\r\n \"table4\": {\r\n \"id@\": \"/table3/table4id\",\r\n },\r\n \"page\": 1,\r\n \"count\": 3\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.59b633e1-8099-48e6-9943-d0e27e50cae7\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"59b633e1-8099-48e6-9943-d0e27e50cae7\",\"requestIndex\":0,\"httpRequestId\":\"a04d8d6c-aad7-4eaa-8fe4-11d3edaf05e7\"},\"type\":\"http\",\"response\":{\"id\":\"1a94a35f-3d89-44ef-8923-0f9c5d54e59d\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 14 Mar 2024 03:53:26 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-15T03:53:27.0136472Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,91,93,34,58,91,123,34,116,97,98,108,101,51,34,58,123,34,105,100,34,58,51,51,51,44,34,116,97,98,108,101,50,105,100,34,58,50,51,44,34,116,101,120,116,34,58,34,117,112,100,97,116,101,51,34,44,34,116,97,98,108,101,52,105,100,34,58,52,52,125,44,34,116,97,98,108,101,50,34,58,123,34,105,100,34,58,50,51,44,34,116,101,120,116,34,58,34,110,101,119,103,101,116,34,44,34,116,97,98,108,101,49,105,100,34,58,51,50,54,53,51,50,51,54,56,53,48,55,53,55,44,34,105,115,68,101,108,101,116,101,34,58,48,125,44,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,51,50,51,54,56,53,48,55,53,55,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,68,69,76,69,84,69,34,125,44,34,116,97,98,108,101,52,34,58,123,34,105,100,34,58,52,52,44,34,116,101,120,116,34,58,34,52,52,34,125,125,44,123,34,116,97,98,108,101,51,34,58,123,34,105,100,34,58,51,51,44,34,116,97,98,108,101,50,105,100,34,58,50,51,44,34,116,101,120,116,34,58,34,117,112,100,97,116,101,51,34,44,34,116,97,98,108,101,52,105,100,34,58,52,51,125,44,34,116,97,98,108,101,50,34,58,123,34,105,100,34,58,50,51,44,34,116,101,120,116,34,58,34,110,101,119,103,101,116,34,44,34,116,97,98,108,101,49,105,100,34,58,51,50,54,53,51,50,51,54,56,53,48,55,53,55,44,34,105,115,68,101,108,101,116,101,34,58,48,125,44,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,51,50,51,54,56,53,48,55,53,55,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,68,69,76,69,84,69,34,125,125,44,123,34,116,97,98,108,101,51,34,58,123,34,105,100,34,58,51,50,44,34,116,97,98,108,101,50,105,100,34,58,50,50,44,34,116,101,120,116,34,58,34,117,112,100,97,116,101,51,34,44,34,116,97,98,108,101,52,105,100,34,58,52,50,125,44,34,116,97,98,108,101,50,34,58,123,34,105,100,34,58,50,50,44,34,116,101,120,116,34,58,34,110,101,119,103,101,116,34,44,34,116,97,98,108,101,49,105,100,34,58,51,50,54,53,51,50,51,56,56,48,48,57,54,53,44,34,105,115,68,101,108,101,116,101,34,58,48,125,44,34,116,97,98,108,101,49,34,58,123,34,73,100,34,58,51,50,54,53,51,50,51,56,56,48,48,57,54,53,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,71,69,84,34,125,44,34,116,97,98,108,101,52,34,58,123,34,105,100,34,58,52,50,44,34,116,101,120,116,34,58,34,52,50,34,125,125,93,44,34,112,97,103,101,34,58,49,44,34,99,111,117,110,116,34,58,51,44,34,109,97,120,34,58,50,44,34,116,111,116,97,108,34,58,52,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,52,32,49,49,58,53,51,58,50,55,34,125]},\"cookie\":[],\"responseTime\":590,\"responseSize\":779,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.1902999877929688,\"wait\":1.2958000302314758,\"dns\":0.8892999887466431,\"tcp\":0.6733999848365784,\"firstByte\":584.8849999904633,\"download\":1.787600040435791,\"process\":0.033399999141693115,\"total\":591.7548000216484}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":316,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"[]\\\": {\\n \\\"table3\\\": {\\n \\\"text\\\": \\\"update3\\\",\\n \\\"@order\\\": \\\"id-\\\"\\n },\\n \\\"table2\\\": {\\n \\\"id@\\\": \\\"/table3/table2id\\\"\\n },\\n \\\"table1\\\": {\\n \\\"@column\\\": \\\"id,httpmethod\\\",\\n \\\"id@\\\": \\\"/table2/table1id\\\"\\n },\\n \\\"table4\\\": {\\n \\\"id@\\\": \\\"/table3/table4id\\\"\\n },\\n \\\"page\\\": 1,\\n \\\"count\\\": 3\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"316\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 14 Mar 2024 03:53:26 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.45\"},{\"key\":\"Admin.NET\",\"value\":\"Admin.NET\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-15T03:53:27.0136472Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710388406828,\"requestStart\":1710388406830,\"offset\":{\"request\":2.1902999877929688,\"socket\":3.4861000180244446,\"lookup\":4.375400006771088,\"connect\":5.048799991607666,\"response\":589.9337999820709,\"end\":591.7214000225067,\"done\":591.7548000216484}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143871046,\"httpApiName\":\"统一查询入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"关联查询\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143975340,"type":"http","path":null,"name":"group by","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"[]\": {\r\n \"table1\": {\r\n \"@column\": \"actionname,httpmethod,max(id)\",\r\n \"@group\": \"actionname,httpmethod\",\r\n },\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.00314312-a9fe-4ad2-859c-b7555f9a0d3e\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"00314312-a9fe-4ad2-859c-b7555f9a0d3e\",\"requestIndex\":0,\"httpRequestId\":\"e6ba19a4-e4f9-4412-9b45-f48e81bb7277\"},\"type\":\"http\",\"response\":{\"id\":\"6134deb2-55f4-419b-9387-38ae669f137f\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Fri, 08 Mar 2024 08:23:12 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86397\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-09T08:14:42.5201051Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,91,93,34,58,91,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,80,111,115,116,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,53,50,49,51,57,56,52,53,52,51,52,49,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,54,53,51,50,51,56,56,48,48,57,54,53,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,81,117,101,114,121,66,121,84,97,98,108,101,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,54,52,55,52,52,57,50,50,54,51,48,57,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,82,101,109,111,118,101,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,54,53,51,49,53,48,50,48,55,53,53,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,54,53,51,50,54,54,50,50,48,51,53,55,125,125,44,123,34,116,97,98,108,101,49,34,58,123,34,65,99,116,105,111,110,78,97,109,101,34,58,34,83,119,97,103,103,101,114,83,117,98,109,105,116,85,114,108,34,44,34,72,116,116,112,77,101,116,104,111,100,34,58,34,80,79,83,84,34,44,34,109,97,120,40,105,100,41,34,58,51,50,54,53,48,52,50,57,50,50,50,50,49,51,125,125,93,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,56,32,49,54,58,50,51,58,49,51,34,125]},\"cookie\":[],\"responseTime\":8399,\"responseSize\":605,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.243699997663498,\"wait\":0.2954000011086464,\"dns\":0,\"tcp\":0,\"firstByte\":8396.081299997866,\"download\":1.9274000003933907,\"process\":0.052000001072883606,\"total\":8400.599799998105}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"get\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":162,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"[]\\\": {\\r\\n \\\"table1\\\": {\\r\\n \\\"@column\\\": \\\"actionname,httpmethod,max(id)\\\",\\r\\n \\\"@group\\\": \\\"actionname,httpmethod\\\",\\r\\n },\\r\\n }\\r\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/get\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"162\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Fri, 08 Mar 2024 08:23:12 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86397\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-09T08:14:42.5201051Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1709886184664,\"requestStart\":1709886184667,\"offset\":{\"request\":2.243699997663498,\"socket\":2.5390999987721443,\"response\":8398.620399996638,\"end\":8400.547799997032,\"lookup\":2.5390999987721443,\"connect\":2.5390999987721443,\"done\":8400.599799998105}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":151219333,\"httpApiCaseId\":143975340,\"httpApiName\":\"统一入口\",\"httpApiPath\":\"/api/aPIJSON/get\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"group by\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{"disabledSystemHeaders":{}},"mockScript":{},"codeSamples":[],"commonResponseStatus":{},"responseChildren":["BLANK.405647728"],"preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}},{"name":"新增","api":{"id":"152848473","method":"post","path":"/api/aPIJSON/post","parameters":{"query":[],"path":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[]},"responses":[{"id":"408753512","name":"Success","code":200,"contentType":"json","jsonSchema":{"$ref":"#/definitions/84275190"}}],"responseExamples":[],"requestBody":{"type":"application/json","parameters":[],"jsonSchema":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275307"},"x-apifox-orders":[]},"example":"{\r\n \"table2\": \r\n {\r\n //\"id\": 236, //如果传id,就用前端id,如果没有,就后端生成\r\n \"text\": \"newget\"\r\n }\r\n\r\n}"},"description":"","tags":["aPIJSON"],"status":"released","serverId":"","operationId":"api-aPIJSON-post-Post","sourceUrl":"","ordering":12,"cases":[{"id":144371019,"type":"http","path":null,"name":"单条","responseId":0,"parameters":{"query":[],"header":[],"cookie":[],"path":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table2\": \r\n {\r\n //\"id\": 236, //如果传id,就用前端id,如果没有,就后端生成\r\n \"text\": \"newget\"\r\n }\r\n\r\n}","type":"application/json"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.04906a0f-2a35-491b-8335-13c224a6e496\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"04906a0f-2a35-491b-8335-13c224a6e496\",\"requestIndex\":0,\"httpRequestId\":\"8b54192a-17b5-4a20-b7c0-115533d436f3\"},\"type\":\"http\",\"response\":{\"id\":\"000965cc-02df-4aae-8b23-18368ab61ecb\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 06:58:40 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86395\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T06:48:49.8348531Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,50,34,58,91,51,50,55,54,49,54,51,48,53,50,52,57,57,55,44,50,51,56,93,44,34,116,97,98,108,101,51,34,58,51,50,55,54,49,54,51,48,53,50,56,48,54,57,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,49,32,49,52,58,53,56,58,52,49,34,125]},\"cookie\":[],\"responseTime\":118,\"responseSize\":150,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.154699981212616,\"wait\":0.394599974155426,\"dns\":0,\"tcp\":0,\"firstByte\":114.57359999418259,\"download\":2.3081000447273254,\"process\":0.08730000257492065,\"total\":119.51829999685287}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"post\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":171,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"table2\\\": [\\n {\\n \\\"text\\\": \\\"newget\\\"\\n },\\n {\\n \\\"id\\\": 238,\\n \\\"text\\\": \\\"newget2\\\"\\n }\\n ],\\n \\\"table3\\\": {\\n \\\"table2id\\\": 238,\\n \\\"text\\\": \\\"newget\\\"\\n }\\n}\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/post\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"171\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 06:58:40 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86395\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T06:48:49.8348531Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710140321198,\"requestStart\":1710140321200,\"offset\":{\"request\":2.154699981212616,\"socket\":2.549299955368042,\"response\":117.12289994955063,\"end\":119.43099999427795,\"lookup\":2.549299955368042,\"connect\":2.549299955368042,\"done\":119.51829999685287}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848473,\"httpApiName\":\"新增\",\"httpApiPath\":\"/api/aPIJSON/post\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"成功\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":144370602,"type":"http","path":null,"name":"多表批量","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table2\": [\r\n {\r\n //\"id\": 236, //如果传id,就用前端id,如果没有,就后端生成\r\n \"text\": \"newget\"\r\n },\r\n {\r\n \"id\": 240,\r\n \"text\": \"newget2\"\r\n }\r\n ],\r\n \"table3\":{\r\n \"table2id\":240,\r\n \"text\":\"newget\"\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.89a3b724-e244-47bd-8332-b18d13b87f39\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"89a3b724-e244-47bd-8332-b18d13b87f39\",\"requestIndex\":0,\"httpRequestId\":\"c4ff5552-4d6b-4816-bac7-951f9acbd466\"},\"type\":\"http\",\"response\":{\"id\":\"a2e99ccb-e877-4151-9d9e-157c49c50e65\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 07:32:24 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T07:30:34.5282454Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,50,34,58,123,34,105,100,34,58,91,51,50,55,54,50,49,52,56,54,49,48,54,50,57,44,50,52,48,93,44,34,99,111,117,110,116,34,58,50,125,44,34,116,97,98,108,101,51,34,58,123,34,105,100,34,58,51,50,55,54,50,49,52,56,54,49,55,53,52,49,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,49,32,49,53,58,51,50,58,50,53,34,125]},\"cookie\":[],\"responseTime\":168,\"responseSize\":174,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":1.51419997215271,\"wait\":0.2290000319480896,\"dns\":0,\"tcp\":0,\"firstByte\":165.6187999844551,\"download\":1.2360000014305115,\"process\":0.05760002136230469,\"total\":168.65560001134872}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"post\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":171,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\n \\\"table2\\\": [\\n {\\n \\\"text\\\": \\\"newget\\\"\\n },\\n {\\n \\\"id\\\": 240,\\n \\\"text\\\": \\\"newget2\\\"\\n }\\n ],\\n \\\"table3\\\": {\\n \\\"table2id\\\": 240,\\n \\\"text\\\": \\\"newget\\\"\\n }\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/post\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"171\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 07:32:24 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T07:30:34.5282454Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710142344933,\"requestStart\":1710142344934,\"offset\":{\"request\":1.51419997215271,\"socket\":1.7432000041007996,\"response\":167.3619999885559,\"end\":168.59799998998642,\"lookup\":1.7432000041007996,\"connect\":1.7432000041007996,\"done\":168.65560001134872}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848473,\"httpApiCaseId\":144370602,\"httpApiName\":\"新增\",\"httpApiPath\":\"/api/aPIJSON/post\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"多表批量\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{"disabledSystemHeaders":{}},"mockScript":{},"codeSamples":[],"commonResponseStatus":{},"responseChildren":["BLANK.408753512"],"preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}},{"name":"修改","api":{"id":"152848474","method":"post","path":"/api/aPIJSON/put","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"408753513","name":"Success","code":200,"contentType":"json","jsonSchema":{"$ref":"#/definitions/84275190"}}],"responseExamples":[],"requestBody":{"type":"application/json","parameters":[],"jsonSchema":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275307"},"x-apifox-orders":[]},"example":""},"description":"","tags":["aPIJSON"],"status":"released","serverId":"","operationId":"api-aPIJSON-put-Post","sourceUrl":"","ordering":18,"cases":[{"id":144398604,"type":"http","path":null,"name":"通过id单条更新","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table3\": {\r\n \"id\": 32762120351813,\r\n \"text\": \"edit\",\r\n \"table2id\":255\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.3c2d26df-ff36-4408-84d1-ba86a03a15d9\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"3c2d26df-ff36-4408-84d1-ba86a03a15d9\",\"requestIndex\":0,\"httpRequestId\":\"01efc573-4df9-4ee4-8f19-329b479d8f83\"},\"type\":\"http\",\"response\":{\"id\":\"c0132dd1-a39d-491a-a497-7a91230bfbc7\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 08:24:50 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T08:24:51.3365034Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,51,34,58,123,34,99,111,100,101,34,58,50,48,48,44,34,109,115,103,34,58,34,115,117,99,99,101,115,115,34,44,34,105,100,34,58,34,51,50,55,54,50,49,50,48,51,53,49,56,49,51,34,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,49,32,49,54,58,50,52,58,53,49,34,125]},\"cookie\":[],\"responseTime\":626,\"responseSize\":156,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.3127999901771545,\"wait\":1.4280999898910522,\"dns\":0.35510003566741943,\"tcp\":0.7379999756813049,\"firstByte\":621.6582000255585,\"download\":1.3226999640464783,\"process\":0.029600024223327637,\"total\":627.8445000052452}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"put\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":108,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"table3\\\": {\\r\\n \\\"id\\\": 32762120351813,\\r\\n \\\"text\\\": \\\"edit\\\",\\r\\n \\\"table2id\\\":255\\r\\n }\\r\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/put\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"108\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 08:24:50 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T08:24:51.3365034Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710145491156,\"requestStart\":1710145491158,\"offset\":{\"request\":2.3127999901771545,\"socket\":3.740899980068207,\"lookup\":4.096000015735626,\"connect\":4.833999991416931,\"response\":626.4922000169754,\"end\":627.8148999810219,\"done\":627.8445000052452}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848474,\"httpApiCaseId\":144398604,\"httpApiName\":\"修改\",\"httpApiPath\":\"/api/aPIJSON/put\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"单条更新\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":144399593,"type":"http","path":null,"name":"多表多id批量更新","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table3\": [\r\n {\r\n \"id\": [31,32,33],\r\n \"text\": \"update3\"\r\n },\r\n {\r\n \"id\": 32762148617541,\r\n \"text\": \"update2\"\r\n }\r\n ],\r\n \"table2\": {\r\n \"id\": 32761658134341,\r\n \"text\": \"update\"\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.a347f44c-82a5-437f-afef-bef75f5f55f4\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"a347f44c-82a5-437f-afef-bef75f5f55f4\",\"requestIndex\":0,\"httpRequestId\":\"1ee88481-8f7b-480f-adf0-3a74acff8426\"},\"type\":\"http\",\"response\":{\"id\":\"6d9b1b3d-167c-4fe8-832e-f336630e6e64\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 09:38:05 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86397\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T09:36:21.4486506Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,51,34,58,123,34,99,111,117,110,116,34,58,52,125,44,34,116,97,98,108,101,50,34,58,123,34,99,111,117,110,116,34,58,49,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,49,32,49,55,58,51,56,58,48,53,34,125]},\"cookie\":[],\"responseTime\":135,\"responseSize\":138,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":1.440500020980835,\"wait\":0.2959999442100525,\"dns\":0,\"tcp\":0,\"firstByte\":132.63910001516342,\"download\":1.2211000323295593,\"process\":0.04819995164871216,\"total\":135.64489996433258}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"put\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":283,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"table3\\\": [\\r\\n {\\r\\n \\\"id\\\": [31,32,33],\\r\\n \\\"text\\\": \\\"update3\\\"\\r\\n },\\r\\n {\\r\\n \\\"id\\\": 32762148617541,\\r\\n \\\"text\\\": \\\"update2\\\"\\r\\n }\\r\\n ],\\r\\n \\\"table2\\\": {\\r\\n \\\"id\\\": 32761658134341,\\r\\n \\\"text\\\": \\\"update\\\"\\r\\n }\\r\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/put\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"283\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 09:38:05 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86397\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T09:36:21.4486506Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710149885351,\"requestStart\":1710149885352,\"offset\":{\"request\":1.440500020980835,\"socket\":1.7364999651908875,\"response\":134.3755999803543,\"end\":135.59670001268387,\"lookup\":1.7364999651908875,\"connect\":1.7364999651908875,\"done\":135.64489996433258}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848474,\"httpApiCaseId\":144399593,\"httpApiName\":\"修改\",\"httpApiPath\":\"/api/aPIJSON/put\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"多表更新\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{"disabledSystemHeaders":{}},"mockScript":{},"codeSamples":[],"commonResponseStatus":{},"responseChildren":[],"preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}},{"name":"删除","api":{"id":"152848475","method":"post","path":"/api/aPIJSON/delete","parameters":{"query":[],"path":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[]},"responses":[{"id":"408753514","name":"Success","code":200,"contentType":"json","jsonSchema":{"$ref":"#/definitions/84275190"}}],"responseExamples":[],"requestBody":{"type":"application/json","parameters":[],"jsonSchema":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275307"},"x-apifox-orders":[]},"example":"{\r\n \"table1\": {\r\n \"id\": [32520112744261,32520179315781,32520200693573],\r\n }\r\n}"},"description":"","tags":["aPIJSON"],"status":"released","serverId":"","operationId":"api-aPIJSON-delete-Post","sourceUrl":"","ordering":24,"cases":[{"id":143343716,"type":"http","path":null,"name":"删除单条数据","responseId":0,"parameters":{"query":[],"header":[],"cookie":[],"path":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table1\": {\r\n \"id\": \"32520494044741\",\r\n }\r\n}","type":"application/json"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.26698a36-85d0-4cbb-b880-abbb4872dddf\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"26698a36-85d0-4cbb-b880-abbb4872dddf\",\"requestIndex\":0,\"httpRequestId\":\"e91c2ef7-03a4-41fa-b114-138973559bec\"},\"type\":\"http\",\"response\":{\"id\":\"a6999ac7-bec5-4b65-a905-42dbe74794e7\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Wed, 06 Mar 2024 09:16:09 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-07T09:15:13.4609976Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,49,34,58,123,34,99,111,100,101,34,58,50,48,48,44,34,109,115,103,34,58,34,115,117,99,99,101,115,115,34,44,34,105,100,34,58,34,51,50,53,50,48,52,57,52,48,52,52,55,52,49,34,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,54,32,49,55,58,49,54,58,48,57,34,125]},\"cookie\":[],\"responseTime\":111,\"responseSize\":156,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":1.5057999789714813,\"wait\":0.2891000211238861,\"dns\":0,\"tcp\":0,\"firstByte\":109.1957999765873,\"download\":1.360700011253357,\"process\":0.034900009632110596,\"total\":112.38629999756813}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"delete\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTE5NzE1MiwibmJmIjoxNzA5MTk3MTUyLCJleHAiOjE3MDk4MDE5NTIsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.96Y2rBegXqiYjuXcXeVyuXV75Cmzu8FpILylDcougt8\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":61,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"table1\\\": {\\r\\n \\\"id\\\": \\\"32520494044741\\\",\\r\\n }\\r\\n}\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTE5NzE1MiwibmJmIjoxNzA5MTk3MTUyLCJleHAiOjE3MDk4MDE5NTIsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.96Y2rBegXqiYjuXcXeVyuXV75Cmzu8FpILylDcougt8\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/delete\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTE5NzE1MiwibmJmIjoxNzA5MTk3MTUyLCJleHAiOjE3MDk4MDE5NTIsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.96Y2rBegXqiYjuXcXeVyuXV75Cmzu8FpILylDcougt8\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"61\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Wed, 06 Mar 2024 09:16:09 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86398\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-07T09:15:13.4609976Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1709716569879,\"requestStart\":1709716569880,\"offset\":{\"request\":1.5057999789714813,\"socket\":1.7949000000953674,\"response\":110.99069997668266,\"end\":112.35139998793602,\"lookup\":1.7949000000953674,\"connect\":1.7949000000953674,\"done\":112.38629999756813}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848475,\"httpApiName\":\"删除\",\"httpApiPath\":\"/api/aPIJSON/delete\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"成功\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143368262,"type":"http","path":null,"name":"批量删除id","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table3\": {\r\n \"id\": [\r\n 32761630528069,\r\n 239\r\n ]\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.c39182fc-fc2f-4357-b3b7-32e41ef1fb57\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"c39182fc-fc2f-4357-b3b7-32e41ef1fb57\",\"requestIndex\":0,\"httpRequestId\":\"7443b4a1-f762-4a24-8dd2-ad33ce1c8da7\"},\"type\":\"http\",\"response\":{\"id\":\"43956c21-adae-41c7-a722-c856c7c7c0aa\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 07:34:26 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86396\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T07:26:16.5966009Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,51,34,58,123,34,105,100,34,58,91,51,50,55,54,49,54,51,48,53,50,56,48,54,57,44,50,51,57,93,44,34,99,111,117,110,116,34,58,49,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,49,49,32,49,53,58,51,52,58,50,55,34,125]},\"cookie\":[],\"responseTime\":158,\"responseSize\":143,\"type\":\"http\",\"tempFilePath\":\"\",\"timingPhases\":{\"prepare\":2.1498000025749207,\"wait\":0.36399996280670166,\"dns\":0,\"tcp\":0,\"firstByte\":156.0023000240326,\"download\":1.451200008392334,\"process\":0.04890000820159912,\"total\":160.01620000600815}},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"delete\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":55,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"table3\\\": {\\r\\n\\\"id\\\":[32761630528069,239]\\r\\n }\\r\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/delete\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"55\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Mon, 11 Mar 2024 07:34:26 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86396\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-12T07:26:16.5966009Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1710142466906,\"requestStart\":1710142466908,\"offset\":{\"request\":2.1498000025749207,\"socket\":2.5137999653816223,\"response\":158.51609998941422,\"end\":159.96729999780655,\"lookup\":2.5137999653816223,\"connect\":2.5137999653816223,\"done\":160.01620000600815}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848475,\"httpApiCaseId\":143368262,\"httpApiName\":\"删除\",\"httpApiPath\":\"/api/aPIJSON/delete\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"批量删除id\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}},{"id":143543753,"type":"http","path":null,"name":"批量删除(匹配条件)","responseId":0,"parameters":{"query":[],"path":[],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"data":"{\r\n \"table1\": {\r\n \"httpmethod\": [\"GET\",\"POST\"],\r\n \"ThreadId\": [37,39],\"Actionname\":\"SwaggerCheckUrl\",\"loglevel\":2.5,\"isdelete\":false\r\n }\r\n}","generateMode":"normal"},"auth":{},"advancedSettings":{"disabledSystemHeaders":{},"isDefaultUrlEncoding":2,"disableUrlEncoding":false},"requestResult":"{\"id\":\"temp.44a9ebb3-1e3d-4049-936a-a412fb468f00\",\"cursor\":{\"position\":0,\"iteration\":0,\"length\":1,\"cycles\":1,\"empty\":false,\"eof\":false,\"bof\":true,\"cr\":false,\"ref\":\"44a9ebb3-1e3d-4049-936a-a412fb468f00\",\"requestIndex\":0,\"httpRequestId\":\"2365b41d-b930-4f24-85d5-12b1198e2c4d\"},\"type\":\"http\",\"response\":{\"id\":\"097a32a0-1cbe-410f-979f-e1170b268276\",\"status\":\"OK\",\"code\":200,\"header\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 07 Mar 2024 10:10:46 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-08T10:10:47.0961001Z\"}],\"trailer\":[],\"stream\":{\"type\":\"Buffer\",\"data\":[123,34,99,111,100,101,34,58,50,48,48,44,34,116,121,112,101,34,58,34,115,117,99,99,101,115,115,34,44,34,109,101,115,115,97,103,101,34,58,34,34,44,34,114,101,115,117,108,116,34,58,123,34,116,97,98,108,101,49,34,58,123,34,104,116,116,112,109,101,116,104,111,100,34,58,91,34,71,69,84,34,44,34,80,79,83,84,34,93,44,34,84,104,114,101,97,100,73,100,34,58,91,51,55,44,51,57,93,44,34,65,99,116,105,111,110,110,97,109,101,34,58,34,83,119,97,103,103,101,114,67,104,101,99,107,85,114,108,34,44,34,108,111,103,108,101,118,101,108,34,58,50,46,53,44,34,105,115,100,101,108,101,116,101,34,58,102,97,108,115,101,44,34,99,111,117,110,116,34,58,48,125,125,44,34,101,120,116,114,97,115,34,58,110,117,108,108,44,34,116,105,109,101,34,58,34,50,48,50,52,45,48,51,45,48,55,32,49,56,58,49,48,58,52,55,34,125]},\"cookie\":[],\"responseTime\":602,\"responseSize\":227,\"type\":\"http\",\"tempFilePath\":\"\"},\"request\":{\"url\":{\"protocol\":\"http\",\"port\":\"5005\",\"path\":[\"api\",\"aPIJSON\",\"delete\"],\"host\":[\"localhost\"],\"query\":[],\"variable\":[]},\"header\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"system\":true},{\"key\":\"Accept\",\"value\":\"*/*\",\"system\":true},{\"key\":\"Host\",\"value\":\"localhost:5005\",\"system\":true},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\",\"system\":true},{\"key\":\"Connection\",\"value\":\"keep-alive\",\"system\":true},{\"key\":\"Content-Length\",\"value\":159,\"system\":true}],\"method\":\"POST\",\"baseUrl\":\"http://localhost:5005\",\"body\":{\"mode\":\"raw\",\"raw\":\"{\\r\\n \\\"table1\\\": {\\r\\n \\\"httpmethod\\\": [\\\"GET\\\",\\\"POST\\\"],\\r\\n \\\"ThreadId\\\": [37,39],\\\"Actionname\\\":\\\"SwaggerCheckUrl\\\",\\\"loglevel\\\":2.5,\\\"isdelete\\\":false\\r\\n }\\r\\n}\",\"generateMode\":\"normal\",\"type\":\"application/json\"},\"auth\":{\"type\":\"bearer\",\"bearer\":[{\"type\":\"any\",\"value\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\",\"key\":\"token\"}]},\"type\":\"http\"},\"history\":{\"execution\":{\"verbose\":false,\"sessions\":{},\"data\":[{\"request\":{\"method\":\"POST\",\"href\":\"http://localhost:5005/api/aPIJSON/delete\",\"headers\":[{\"key\":\"User-Agent\",\"value\":\"Apifox/1.0.0 (https://apifox.com)\"},{\"key\":\"Content-Type\",\"value\":\"application/json\"},{\"key\":\"Authorization\",\"value\":\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec\"},{\"key\":\"Accept\",\"value\":\"*/*\"},{\"key\":\"Host\",\"value\":\"localhost:5005\"},{\"key\":\"Accept-Encoding\",\"value\":\"gzip, deflate, br\"},{\"key\":\"Connection\",\"value\":\"keep-alive\"},{\"key\":\"Content-Length\",\"value\":\"159\"}],\"httpVersion\":\"1.1\"},\"response\":{\"statusCode\":200,\"headers\":[{\"key\":\"Content-Type\",\"value\":\"application/json; charset=utf-8\"},{\"key\":\"Date\",\"value\":\"Thu, 07 Mar 2024 10:10:46 GMT\"},{\"key\":\"Server\",\"value\":\"Kestrel\"},{\"key\":\"Content-Language\",\"value\":\"zh-CN\"},{\"key\":\"Transfer-Encoding\",\"value\":\"chunked\"},{\"key\":\"environment\",\"value\":\"Development\"},{\"key\":\"Furion\",\"value\":\"4.9.1.32\"},{\"key\":\"X-Rate-Limit-Limit\",\"value\":\"1d\"},{\"key\":\"X-Rate-Limit-Remaining\",\"value\":\"86399\"},{\"key\":\"X-Rate-Limit-Reset\",\"value\":\"2024-03-08T10:10:47.0961001Z\"}],\"httpVersion\":\"1.1\"},\"timings\":{\"start\":1709806246909,\"requestStart\":1709806246912,\"offset\":{\"request\":2.155799984931946,\"socket\":3.6095999479293823,\"lookup\":3.9983999729156494,\"connect\":4.8549999594688416,\"response\":602.0516999959946,\"end\":603.2913999557495,\"done\":603.3172999620438}}}]}},\"responseValidation\":{},\"passed\":true,\"metaInfo\":{\"httpApiId\":152848475,\"httpApiCaseId\":143543753,\"httpApiName\":\"删除\",\"httpApiPath\":\"/api/aPIJSON/delete\",\"httpApiMethod\":\"post\",\"httpApiCaseName\":\"批量删除2\"}}","preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{"disabledSystemHeaders":{}},"mockScript":{},"codeSamples":[],"commonResponseStatus":{},"responseChildren":["BLANK.408753514"],"preProcessors":[],"postProcessors":[],"inheritPostProcessors":{},"inheritPreProcessors":{}}}]}]}],"socketCollection":[],"docCollection":[],"schemaCollection":[{"name":"根目录","items":[{"name":"Schemas","items":[{"name":"AccountTypeEnum","displayName":"","id":"#/definitions/84275167","description":"账号类型枚举
 会员 Member = 666
 普通账号 NormalUser = 777
 系统管理员 SysAdmin = 888
 超级管理员 SuperAdmin = 999
","schema":{"jsonSchema":{"enum":[666,777,888,999],"type":"integer","description":"账号类型枚举
 会员 Member = 666
 普通账号 NormalUser = 777
 系统管理员 SysAdmin = 888
 超级管理员 SuperAdmin = 999
","format":"int32"}}},{"name":"AddCodeGenInput","displayName":"","id":"#/definitions/84275168","description":"","schema":{"jsonSchema":{"required":["authorName","busName","generateType","menuPid","nameSpace","tableName"],"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"className":{"type":["string","null"],"description":"类名"},"tablePrefix":{"type":["string","null"],"description":"是否移除表前缀"},"configId":{"type":["string","null"],"description":"库定位器名"},"dbName":{"type":["string","null"],"description":"数据库名(保留字段)"},"dbType":{"type":["string","null"],"description":"数据库类型"},"connectionString":{"type":["string","null"],"description":"数据库链接"},"tableComment":{"type":["string","null"],"description":"功能名(数据库表名称)"},"menuApplication":{"type":["string","null"],"description":"菜单应用分类(应用编码)"},"printType":{"type":["string","null"],"description":"支持打印类型"},"printName":{"type":["string","null"],"description":"打印模版名称"},"tableName":{"minLength":1,"type":"string","description":"数据库表名"},"busName":{"minLength":1,"type":"string","description":"业务名(业务代码包名称)"},"nameSpace":{"minLength":1,"type":"string","description":"命名空间"},"authorName":{"minLength":1,"type":"string","description":"作者姓名"},"generateType":{"minLength":1,"type":"string","description":"生成方式"},"menuPid":{"type":"integer","description":"菜单父级","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","className","tablePrefix","configId","dbName","dbType","connectionString","tableComment","menuApplication","printType","printName","tableName","busName","nameSpace","authorName","generateType","menuPid"]}}},{"name":"AddConfigInput","displayName":"","id":"#/definitions/84275169","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"value":{"maxLength":64,"type":["string","null"],"description":"属性值"},"sysFlag":{"$ref":"#/definitions/84275444"},"groupCode":{"maxLength":64,"type":["string","null"],"description":"分组编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","value","sysFlag","groupCode","orderNo","remark"]}}},{"name":"AddDictDataInput","displayName":"","id":"#/definitions/84275170","description":"","schema":{"jsonSchema":{"required":["code","value"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"dictTypeId":{"type":"integer","description":"字典类型Id","format":"int64"},"value":{"maxLength":128,"minLength":1,"type":"string","description":"值"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"tagType":{"maxLength":16,"type":["string","null"],"description":"显示样式-标签颜色"},"styleSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Style(控制显示样式)"},"classSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Class(控制显示样式)"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":2048,"type":["string","null"],"description":"备注"},"extData":{"type":["string","null"],"description":"拓展数据(保存业务功能的配置项)"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","dictTypeId","value","code","tagType","styleSetting","classSetting","orderNo","remark","extData","status"]}}},{"name":"AddDictTypeInput","displayName":"","id":"#/definitions/84275171","description":"","schema":{"jsonSchema":{"required":["code","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275379"},"description":"字典值集合"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","orderNo","remark","status","children"]}}},{"name":"AddJobDetailInput","displayName":"","id":"#/definitions/84275172","description":"","schema":{"jsonSchema":{"required":["jobId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"groupName":{"maxLength":128,"type":["string","null"],"description":"组名称"},"jobType":{"maxLength":128,"type":["string","null"],"description":"作业类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"concurrent":{"type":"boolean","description":"是否并行执行"},"includeAnnotations":{"type":"boolean","description":"是否扫描特性触发器"},"properties":{"type":["string","null"],"description":"额外数据"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createType":{"$ref":"#/definitions/84275308"},"scriptCode":{"type":["string","null"],"description":"脚本代码"},"jobId":{"minLength":2,"type":"string","description":"作业Id"}},"additionalProperties":false,"x-apifox-orders":["id","groupName","jobType","assemblyName","description","concurrent","includeAnnotations","properties","updatedTime","createType","scriptCode","jobId"]}}},{"name":"AddJobTriggerInput","displayName":"","id":"#/definitions/84275173","description":"","schema":{"jsonSchema":{"required":["jobId","triggerId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"triggerType":{"maxLength":128,"type":["string","null"],"description":"触发器类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"args":{"maxLength":128,"type":["string","null"],"description":"参数"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"status":{"$ref":"#/definitions/84275411"},"startTime":{"type":["string","null"],"description":"起始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"},"lastRunTime":{"type":["string","null"],"description":"最近运行时间","format":"date-time"},"nextRunTime":{"type":["string","null"],"description":"下一次运行时间","format":"date-time"},"numberOfRuns":{"type":"integer","description":"触发次数","format":"int64"},"maxNumberOfRuns":{"type":"integer","description":"最大触发次数(0:不限制,n:N次)","format":"int64"},"numberOfErrors":{"type":"integer","description":"出错次数","format":"int64"},"maxNumberOfErrors":{"type":"integer","description":"最大出错次数(0:不限制,n:N次)","format":"int64"},"numRetries":{"type":"integer","description":"重试次数","format":"int32"},"retryTimeout":{"type":"integer","description":"重试间隔时间(ms)","format":"int32"},"startNow":{"type":"boolean","description":"是否立即启动"},"runOnStart":{"type":"boolean","description":"是否启动时执行一次"},"resetOnlyOnce":{"type":"boolean","description":"是否在启动时重置最大触发次数等于一次的作业"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"jobId":{"minLength":2,"type":"string","description":"作业Id"},"triggerId":{"minLength":2,"type":"string","description":"触发器Id"}},"additionalProperties":false,"x-apifox-orders":["id","triggerType","assemblyName","args","description","status","startTime","endTime","lastRunTime","nextRunTime","numberOfRuns","maxNumberOfRuns","numberOfErrors","maxNumberOfErrors","numRetries","retryTimeout","startNow","runOnStart","resetOnlyOnce","updatedTime","jobId","triggerId"]}}},{"name":"AddMenuInput","displayName":"","id":"#/definitions/84275174","description":"","schema":{"jsonSchema":{"required":["title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"pid":{"type":"integer","description":"父Id","format":"int64"},"type":{"$ref":"#/definitions/84275319"},"name":{"maxLength":64,"type":["string","null"],"description":"路由名称"},"path":{"maxLength":128,"type":["string","null"],"description":"路由地址"},"component":{"maxLength":128,"type":["string","null"],"description":"组件路径"},"redirect":{"maxLength":128,"type":["string","null"],"description":"重定向"},"permission":{"maxLength":128,"type":["string","null"],"description":"权限标识"},"icon":{"maxLength":128,"type":["string","null"],"description":"图标"},"isIframe":{"type":"boolean","description":"是否内嵌"},"outLink":{"maxLength":256,"type":["string","null"],"description":"外链链接"},"isHide":{"type":"boolean","description":"是否隐藏"},"isKeepAlive":{"type":"boolean","description":"是否缓存"},"isAffix":{"type":"boolean","description":"是否固定"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275390"},"description":"菜单子项"},"title":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","pid","type","name","path","component","redirect","permission","icon","isIframe","outLink","isHide","isKeepAlive","isAffix","orderNo","status","remark","children","title"]}}},{"name":"AddNoticeInput","displayName":"","id":"#/definitions/84275175","description":"","schema":{"jsonSchema":{"required":["content","title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"title":{"maxLength":32,"minLength":1,"type":"string","description":"标题"},"content":{"minLength":1,"type":"string","description":"内容"},"type":{"$ref":"#/definitions/84275325"},"publicUserId":{"type":"integer","description":"发布人Id","format":"int64"},"publicUserName":{"maxLength":32,"type":["string","null"],"description":"发布人姓名"},"publicOrgId":{"type":"integer","description":"发布机构Id","format":"int64"},"publicOrgName":{"maxLength":64,"type":["string","null"],"description":"发布机构名称"},"publicTime":{"type":["string","null"],"description":"发布时间","format":"date-time"},"cancelTime":{"type":["string","null"],"description":"撤回时间","format":"date-time"},"status":{"$ref":"#/definitions/84275324"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","title","content","type","publicUserId","publicUserName","publicOrgId","publicOrgName","publicTime","cancelTime","status"]}}},{"name":"AddOpenAccessInput","displayName":"","id":"#/definitions/84275176","description":"","schema":{"jsonSchema":{"required":["accessKey","accessSecret","bindUserId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"bindTenantId":{"type":"integer","description":"绑定租户Id","format":"int64"},"accessKey":{"minLength":1,"type":"string","description":"身份标识"},"accessSecret":{"minLength":1,"type":"string","description":"密钥"},"bindUserId":{"type":"integer","description":"绑定用户Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","bindTenantId","accessKey","accessSecret","bindUserId"]}}},{"name":"AddOrgInput","displayName":"","id":"#/definitions/84275177","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"level":{"type":["integer","null"],"description":"级别","format":"int32"},"type":{"maxLength":64,"type":["string","null"],"description":"机构类型-数据字典"},"directorId":{"type":["integer","null"],"description":"负责人Id","format":"int64"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275395"},"description":"机构子项"},"disabled":{"type":"boolean","description":"是否禁止选中"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","pid","code","level","type","directorId","orderNo","status","remark","children","disabled","name"]}}},{"name":"AddPluginInput","displayName":"","id":"#/definitions/84275178","description":"","schema":{"jsonSchema":{"required":["csharpCode","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"csharpCode":{"minLength":1,"type":"string","description":"C#代码"},"assemblyName":{"maxLength":512,"type":["string","null"],"description":"程序集名称"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","csharpCode","assemblyName","orderNo","status","remark","name"]}}},{"name":"AddPosInput","displayName":"","id":"#/definitions/84275179","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","code","orderNo","remark","status","name"]}}},{"name":"AddPrintInput","displayName":"","id":"#/definitions/84275180","description":"","schema":{"jsonSchema":{"required":["name","template"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"template":{"minLength":1,"type":"string","description":"打印模板"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","template","orderNo","status","remark","name"]}}},{"name":"AddRegionInput","displayName":"","id":"#/definitions/84275181","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"shortName":{"maxLength":32,"type":["string","null"],"description":"简称"},"mergerName":{"maxLength":64,"type":["string","null"],"description":"组合名"},"code":{"maxLength":32,"type":["string","null"],"description":"行政代码"},"zipCode":{"maxLength":6,"type":["string","null"],"description":"邮政编码"},"cityCode":{"maxLength":6,"type":["string","null"],"description":"区号"},"level":{"type":"integer","description":"层级","format":"int32"},"pinYin":{"maxLength":128,"type":["string","null"],"description":"拼音"},"lng":{"type":"number","description":"经度","format":"float"},"lat":{"type":"number","description":"维度","format":"float"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275399"},"description":"机构子项"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","pid","shortName","mergerName","code","zipCode","cityCode","level","pinYin","lng","lat","orderNo","remark","children","name"]}}},{"name":"AddRoleInput","displayName":"","id":"#/definitions/84275182","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"dataScope":{"$ref":"#/definitions/84275269"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"name":{"minLength":1,"type":"string","description":"名称"},"menuIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"菜单Id集合"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","code","orderNo","dataScope","remark","status","name","menuIdList"]}}},{"name":"AddSubscribeMessageTemplateInput","displayName":"","id":"#/definitions/84275183","description":"增加订阅消息模板","schema":{"jsonSchema":{"required":["keyworkIdList","sceneDescription","templateTitleId"],"type":"object","properties":{"templateTitleId":{"minLength":1,"type":"string","description":"模板标题Id"},"keyworkIdList":{"type":"array","items":{"type":"integer","format":"int32"},"description":"模板关键词列表,例如 [3,5,4]"},"sceneDescription":{"minLength":1,"type":"string","description":"服务场景描述,15个字以内"}},"additionalProperties":false,"description":"增加订阅消息模板","x-apifox-orders":["templateTitleId","keyworkIdList","sceneDescription"]}}},{"name":"AddTenantInput","displayName":"","id":"#/definitions/84275184","description":"","schema":{"jsonSchema":{"required":["adminAccount","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"orgId":{"type":"integer","description":"机构Id","format":"int64"},"host":{"maxLength":128,"type":["string","null"],"description":"主机"},"tenantType":{"$ref":"#/definitions/84275409"},"dbType":{"$ref":"#/definitions/84275276"},"connection":{"maxLength":256,"type":["string","null"],"description":"数据库连接"},"configId":{"maxLength":64,"type":["string","null"],"description":"数据库标识"},"slaveConnections":{"type":["string","null"],"description":"从库连接/读写分离"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"email":{"type":["string","null"],"description":"电子邮箱"},"phone":{"type":["string","null"],"description":"电话"},"name":{"minLength":2,"type":"string","description":"租户名称"},"adminAccount":{"minLength":3,"type":"string","description":"租管账号"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","userId","orgId","host","tenantType","dbType","connection","configId","slaveConnections","orderNo","remark","status","email","phone","name","adminAccount"]}}},{"name":"AddUserInput","displayName":"","id":"#/definitions/84275185","description":"增加用户输入参数","schema":{"jsonSchema":{"required":["account","realName"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"nickName":{"maxLength":32,"type":["string","null"],"description":"昵称"},"avatar":{"maxLength":512,"type":["string","null"],"description":"头像"},"sex":{"$ref":"#/definitions/84275305"},"age":{"type":"integer","description":"年龄","format":"int32"},"birthday":{"type":["string","null"],"description":"出生日期","format":"date-time"},"nation":{"maxLength":32,"type":["string","null"],"description":"民族"},"phone":{"maxLength":16,"type":["string","null"],"description":"手机号码"},"cardType":{"$ref":"#/definitions/84275258"},"idCardNum":{"maxLength":32,"type":["string","null"],"description":"身份证号"},"email":{"maxLength":64,"type":["string","null"],"description":"邮箱"},"address":{"maxLength":256,"type":["string","null"],"description":"地址"},"cultureLevel":{"$ref":"#/definitions/84275267"},"politicalOutlook":{"maxLength":16,"type":["string","null"],"description":"政治面貌"},"college":{"maxLength":128,"type":["string","null"],"description":"毕业院校"},"officePhone":{"maxLength":16,"type":["string","null"],"description":"办公电话"},"emergencyContact":{"maxLength":32,"type":["string","null"],"description":"紧急联系人"},"emergencyPhone":{"maxLength":16,"type":["string","null"],"description":"紧急联系人电话"},"emergencyAddress":{"maxLength":256,"type":["string","null"],"description":"紧急联系人地址"},"introduction":{"maxLength":512,"type":["string","null"],"description":"个人简介"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"accountType":{"$ref":"#/definitions/84275167"},"orgId":{"type":"integer","description":"直属机构Id","format":"int64"},"sysOrg":{"$ref":"#/definitions/84275395"},"managerUserId":{"type":["integer","null"],"description":"直属主管Id","format":"int64"},"posId":{"type":"integer","description":"职位Id","format":"int64"},"jobNum":{"maxLength":32,"type":["string","null"],"description":"工号"},"posLevel":{"maxLength":32,"type":["string","null"],"description":"职级"},"posTitle":{"maxLength":32,"type":["string","null"],"description":"职称"},"expertise":{"maxLength":32,"type":["string","null"],"description":"擅长领域"},"officeZone":{"maxLength":32,"type":["string","null"],"description":"办公区域"},"office":{"maxLength":32,"type":["string","null"],"description":"办公室"},"joinDate":{"type":["string","null"],"description":"入职日期","format":"date-time"},"lastLoginIp":{"maxLength":256,"type":["string","null"],"description":"最新登录Ip"},"lastLoginAddress":{"maxLength":128,"type":["string","null"],"description":"最新登录地点"},"lastLoginTime":{"type":["string","null"],"description":"最新登录时间","format":"date-time"},"lastLoginDevice":{"maxLength":128,"type":["string","null"],"description":"最新登录设备"},"signature":{"maxLength":512,"type":["string","null"],"description":"电子签名"},"account":{"minLength":1,"type":"string","description":"账号"},"realName":{"minLength":1,"type":"string","description":"真实姓名"},"roleIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"角色集合"},"extOrgIdList":{"type":["array","null"],"items":{"$ref":"#/definitions/84275402"},"description":"扩展机构集合"}},"additionalProperties":false,"description":"增加用户输入参数","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","nickName","avatar","sex","age","birthday","nation","phone","cardType","idCardNum","email","address","cultureLevel","politicalOutlook","college","officePhone","emergencyContact","emergencyPhone","emergencyAddress","introduction","orderNo","status","remark","accountType","orgId","sysOrg","managerUserId","posId","jobNum","posLevel","posTitle","expertise","officeZone","office","joinDate","lastLoginIp","lastLoginAddress","lastLoginTime","lastLoginDevice","signature","account","realName","roleIdList","extOrgIdList"]}}},{"name":"AdminResult_Boolean","displayName":"","id":"#/definitions/84275186","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":"boolean","description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_IActionResult","displayName":"","id":"#/definitions/84275187","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275306"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_Int32","displayName":"","id":"#/definitions/84275188","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":"integer","description":"数据","format":"int32"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_Int64","displayName":"","id":"#/definitions/84275189","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":"integer","description":"数据","format":"int64"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_JObject","displayName":"","id":"#/definitions/84275190","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["object","null"],"additionalProperties":{"$ref":"#/definitions/84275307"},"description":"数据","x-apifox-orders":[]},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_ApiOutput","displayName":"","id":"#/definitions/84275191","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275257"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_CodeGenConfig","displayName":"","id":"#/definitions/84275192","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275261"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_ColumnOuput","displayName":"","id":"#/definitions/84275193","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275263"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_ConstOutput","displayName":"","id":"#/definitions/84275194","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275264"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_DatabaseOutput","displayName":"","id":"#/definitions/84275195","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275270"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_DbColumnOutput","displayName":"","id":"#/definitions/84275196","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275272"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_DbTableInfo","displayName":"","id":"#/definitions/84275197","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275274"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_EnumEntity","displayName":"","id":"#/definitions/84275198","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275301"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_EnumTypeOutput","displayName":"","id":"#/definitions/84275199","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275302"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_Int64","displayName":"","id":"#/definitions/84275200","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_MenuOutput","displayName":"","id":"#/definitions/84275201","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275318"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_RoleOutput","displayName":"","id":"#/definitions/84275202","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275349"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_String","displayName":"","id":"#/definitions/84275203","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"type":"string"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysConfig","displayName":"","id":"#/definitions/84275204","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275378"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysDictData","displayName":"","id":"#/definitions/84275205","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275379"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysDictType","displayName":"","id":"#/definitions/84275206","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275380"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysFile","displayName":"","id":"#/definitions/84275207","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275381"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysJobCluster","displayName":"","id":"#/definitions/84275208","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275382"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysJobTrigger","displayName":"","id":"#/definitions/84275209","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275384"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysMenu","displayName":"","id":"#/definitions/84275210","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275390"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysNotice","displayName":"","id":"#/definitions/84275211","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275392"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysOrg","displayName":"","id":"#/definitions/84275212","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275395"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysPos","displayName":"","id":"#/definitions/84275213","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275397"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysRegion","displayName":"","id":"#/definitions/84275214","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275399"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysUser","displayName":"","id":"#/definitions/84275215","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275401"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_SysUserExtOrg","displayName":"","id":"#/definitions/84275216","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275402"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_List_TableOutput","displayName":"","id":"#/definitions/84275217","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["array","null"],"items":{"$ref":"#/definitions/84275405"},"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_LoginOutput","displayName":"","id":"#/definitions/84275218","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275315"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_LoginUserOutput","displayName":"","id":"#/definitions/84275219","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275317"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_Object","displayName":"","id":"#/definitions/84275220","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"additionalProperties":false,"description":"数据","type":"null"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SmKeyPairOutput","displayName":"","id":"#/definitions/84275221","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275352"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_JobDetailOutput","displayName":"","id":"#/definitions/84275222","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275353"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_OpenAccessOutput","displayName":"","id":"#/definitions/84275223","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275354"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysCodeGen","displayName":"","id":"#/definitions/84275224","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275355"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysConfig","displayName":"","id":"#/definitions/84275225","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275356"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysDictData","displayName":"","id":"#/definitions/84275226","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275357"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysDictType","displayName":"","id":"#/definitions/84275227","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275358"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysFile","displayName":"","id":"#/definitions/84275228","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275359"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysJobTriggerRecord","displayName":"","id":"#/definitions/84275229","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275360"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysLogDiff","displayName":"","id":"#/definitions/84275230","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275361"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysLogEx","displayName":"","id":"#/definitions/84275231","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275362"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysLogOp","displayName":"","id":"#/definitions/84275232","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275363"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysLogVis","displayName":"","id":"#/definitions/84275233","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275364"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysNotice","displayName":"","id":"#/definitions/84275234","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275365"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysNoticeUser","displayName":"","id":"#/definitions/84275235","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275366"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysOnlineUser","displayName":"","id":"#/definitions/84275236","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275367"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysPlugin","displayName":"","id":"#/definitions/84275237","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275368"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysPrint","displayName":"","id":"#/definitions/84275238","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275369"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysRegion","displayName":"","id":"#/definitions/84275239","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275370"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysRole","displayName":"","id":"#/definitions/84275240","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275371"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_SysWechatUser","displayName":"","id":"#/definitions/84275241","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275372"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_TenantOutput","displayName":"","id":"#/definitions/84275242","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275373"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SqlSugarPagedList_UserOutput","displayName":"","id":"#/definitions/84275243","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275374"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_String","displayName":"","id":"#/definitions/84275244","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"type":["string","null"],"description":"数据"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysCodeGen","displayName":"","id":"#/definitions/84275245","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275376"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysCodeGenConfig","displayName":"","id":"#/definitions/84275246","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275377"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysConfig","displayName":"","id":"#/definitions/84275247","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275378"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysDictData","displayName":"","id":"#/definitions/84275248","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275379"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysDictType","displayName":"","id":"#/definitions/84275249","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275380"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysFile","displayName":"","id":"#/definitions/84275250","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275381"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysPrint","displayName":"","id":"#/definitions/84275251","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275398"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysUser","displayName":"","id":"#/definitions/84275252","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275401"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_SysWechatPay","displayName":"","id":"#/definitions/84275253","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275403"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_WechatPayOutput","displayName":"","id":"#/definitions/84275254","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275436"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_WxOpenIdOutput","displayName":"","id":"#/definitions/84275255","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275442"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"AdminResult_WxPhoneOutput","displayName":"","id":"#/definitions/84275256","description":"全局返回结果","schema":{"jsonSchema":{"type":"object","properties":{"code":{"type":"integer","description":"状态码","format":"int32"},"type":{"type":["string","null"],"description":"类型success、warning、error"},"message":{"type":["string","null"],"description":"错误信息"},"result":{"$ref":"#/definitions/84275443"},"extras":{"additionalProperties":false,"description":"附加数据","type":"null"},"time":{"type":"string","description":"时间","format":"date-time"}},"additionalProperties":false,"description":"全局返回结果","x-apifox-orders":["code","type","message","result","extras","time"]}}},{"name":"ApiOutput","displayName":"","id":"#/definitions/84275257","description":"接口/动态API输出","schema":{"jsonSchema":{"type":"object","properties":{"groupName":{"type":["string","null"],"description":"组名称"},"displayName":{"type":["string","null"],"description":"接口名称"},"routeName":{"type":["string","null"],"description":"路由名称"}},"additionalProperties":false,"description":"接口/动态API输出","x-apifox-orders":["groupName","displayName","routeName"]}}},{"name":"CardTypeEnum","displayName":"","id":"#/definitions/84275258","description":"证件类型枚举
 身份证 IdCard = 0
 护照 PassportCard = 1
 出生证 BirthCard = 2
 港澳台通行证 GatCard = 3
 外国人居留证 ForeignCard = 4
 营业执照 License = 5
","schema":{"jsonSchema":{"enum":[0,1,2,3,4,5],"type":"integer","description":"证件类型枚举
 身份证 IdCard = 0
 护照 PassportCard = 1
 出生证 BirthCard = 2
 港澳台通行证 GatCard = 3
 外国人居留证 ForeignCard = 4
 营业执照 License = 5
","format":"int32"}}},{"name":"ChangePwdInput","displayName":"","id":"#/definitions/84275259","description":"修改用户密码输入参数","schema":{"jsonSchema":{"required":["passwordNew","passwordOld"],"type":"object","properties":{"passwordOld":{"minLength":1,"type":"string","description":"当前密码"},"passwordNew":{"maxLength":20,"minLength":5,"type":"string","description":"新密码"}},"additionalProperties":false,"description":"修改用户密码输入参数","x-apifox-orders":["passwordOld","passwordNew"]}}},{"name":"ClusterStatus","displayName":"","id":"#/definitions/84275260","description":"
  Crashed = 0
  Working = 1
  Waiting = 2
","schema":{"jsonSchema":{"enum":[0,1,2],"type":"integer","description":"
  Crashed = 0
  Working = 1
  Waiting = 2
","format":"int32"}}},{"name":"CodeGenConfig","displayName":"","id":"#/definitions/84275261","description":"代码生成详细配置参数","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"codeGenId":{"type":"integer","description":"代码生成主表ID","format":"int64"},"columnName":{"type":["string","null"],"description":"数据库字段名"},"propertyName":{"type":["string","null"],"description":"实体属性名"},"columnLength":{"type":"integer","description":"字段数据长度","format":"int32"},"lowerPropertyName":{"type":["string","null"],"description":"数据库字段名(首字母小写)","readOnly":true},"columnComment":{"type":["string","null"],"description":"字段描述"},"netType":{"type":["string","null"],"description":".NET类型"},"effectType":{"type":["string","null"],"description":"作用类型(字典)"},"fkEntityName":{"type":["string","null"],"description":"外键实体名称"},"fkTableName":{"type":["string","null"],"description":"外键表名称"},"lowerFkEntityName":{"type":["string","null"],"description":"外键实体名称(首字母小写)","readOnly":true},"fkColumnName":{"type":["string","null"],"description":"外键显示字段"},"lowerFkColumnName":{"type":["string","null"],"description":"外键显示字段(首字母小写)","readOnly":true},"fkColumnNetType":{"type":["string","null"],"description":"外键显示字段.NET类型"},"dictTypeCode":{"type":["string","null"],"description":"字典code"},"whetherRetract":{"type":["string","null"],"description":"列表是否缩进(字典)"},"whetherRequired":{"type":["string","null"],"description":"是否必填(字典)"},"whetherSortable":{"type":["string","null"],"description":"是否可排序(字典)"},"queryWhether":{"type":["string","null"],"description":"是否是查询条件"},"queryType":{"type":["string","null"],"description":"查询方式"},"whetherTable":{"type":["string","null"],"description":"列表显示"},"whetherAddUpdate":{"type":["string","null"],"description":"增改"},"columnKey":{"type":["string","null"],"description":"主外键"},"dataType":{"type":["string","null"],"description":"数据库中类型(物理类型)"},"whetherCommon":{"type":["string","null"],"description":"是否是通用字段"},"tableNickName":{"type":["string","null"],"description":"表的别名 Table as XXX","readOnly":true},"displayColumn":{"type":["string","null"],"description":"显示文本字段"},"valueColumn":{"type":["string","null"],"description":"选中值字段"},"pidColumn":{"type":["string","null"],"description":"父级字段"},"orderNo":{"type":"integer","description":"排序","format":"int32"}},"additionalProperties":false,"description":"代码生成详细配置参数","x-apifox-orders":["id","codeGenId","columnName","propertyName","columnLength","lowerPropertyName","columnComment","netType","effectType","fkEntityName","fkTableName","lowerFkEntityName","fkColumnName","lowerFkColumnName","fkColumnNetType","dictTypeCode","whetherRetract","whetherRequired","whetherSortable","queryWhether","queryType","whetherTable","whetherAddUpdate","columnKey","dataType","whetherCommon","tableNickName","displayColumn","valueColumn","pidColumn","orderNo"]}}},{"name":"CodeGenInput","displayName":"","id":"#/definitions/84275262","description":"代码生成参数类","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"authorName":{"type":["string","null"],"description":"作者姓名"},"className":{"type":["string","null"],"description":"类名"},"tablePrefix":{"type":["string","null"],"description":"是否移除表前缀"},"configId":{"type":["string","null"],"description":"库定位器名"},"dbName":{"type":["string","null"],"description":"数据库名(保留字段)"},"dbType":{"type":["string","null"],"description":"数据库类型"},"connectionString":{"type":["string","null"],"description":"数据库链接"},"generateType":{"type":["string","null"],"description":"生成方式"},"tableName":{"type":["string","null"],"description":"数据库表名"},"nameSpace":{"type":["string","null"],"description":"命名空间"},"busName":{"type":["string","null"],"description":"业务名(业务代码包名称)"},"tableComment":{"type":["string","null"],"description":"功能名(数据库表名称)"},"menuApplication":{"type":["string","null"],"description":"菜单应用分类(应用编码)"},"menuPid":{"type":"integer","description":"菜单父级","format":"int64"},"printType":{"type":["string","null"],"description":"支持打印类型"},"printName":{"type":["string","null"],"description":"打印模版名称"}},"additionalProperties":false,"description":"代码生成参数类","x-apifox-orders":["page","pageSize","field","order","descStr","authorName","className","tablePrefix","configId","dbName","dbType","connectionString","generateType","tableName","nameSpace","busName","tableComment","menuApplication","menuPid","printType","printName"]}}},{"name":"ColumnOuput","displayName":"","id":"#/definitions/84275263","description":"数据库表列","schema":{"jsonSchema":{"type":"object","properties":{"columnName":{"type":["string","null"],"description":"字段名"},"propertyName":{"type":["string","null"],"description":"实体的Property名"},"columnLength":{"type":"integer","description":"字段数据长度","format":"int32"},"dataType":{"type":["string","null"],"description":"数据库中类型"},"isPrimarykey":{"type":"boolean","description":"是否为主键"},"isNullable":{"type":"boolean","description":"是否允许为空"},"netType":{"type":["string","null"],"description":".NET字段类型"},"columnComment":{"type":["string","null"],"description":"字段描述"},"columnKey":{"type":["string","null"],"description":"主外键"}},"additionalProperties":false,"description":"数据库表列","x-apifox-orders":["columnName","propertyName","columnLength","dataType","isPrimarykey","isNullable","netType","columnComment","columnKey"]}}},{"name":"ConstOutput","displayName":"","id":"#/definitions/84275264","description":"","schema":{"jsonSchema":{"type":"object","properties":{"name":{"type":["string","null"],"description":"名称"},"code":{"additionalProperties":false,"description":"编码","type":"null"},"data":{"additionalProperties":false,"description":"扩展字段","type":"null"}},"additionalProperties":false,"x-apifox-orders":["name","code","data"]}}},{"name":"CreateEntityInput","displayName":"","id":"#/definitions/84275265","description":"","schema":{"jsonSchema":{"type":"object","properties":{"tableName":{"type":["string","null"],"description":"表名","examples":["student"]},"entityName":{"type":["string","null"],"description":"实体名","examples":["Student"]},"baseClassName":{"type":["string","null"],"description":"基类名","examples":["AutoIncrementEntity"]},"position":{"type":["string","null"],"description":"导出位置","examples":["Web.Application"]},"configId":{"type":["string","null"],"description":"库标识"}},"additionalProperties":false,"x-apifox-orders":["tableName","entityName","baseClassName","position","configId"]}}},{"name":"CreateSeedDataInput","displayName":"","id":"#/definitions/84275266","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"],"description":"库标识"},"tableName":{"type":["string","null"],"description":"表名","examples":["student"]},"entityName":{"type":["string","null"],"description":"实体名称","examples":["Student"]},"seedDataName":{"type":["string","null"],"description":"种子名称","examples":["Student"]},"position":{"type":["string","null"],"description":"导出位置","examples":["Web.Application"]},"suffix":{"type":["string","null"],"description":"后缀","examples":["Web.Application"]}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","entityName","seedDataName","position","suffix"]}}},{"name":"CultureLevelEnum","displayName":"","id":"#/definitions/84275267","description":"文化程度枚举
 其他 Level0 = 0
 小学 Level1 = 1
 初中 Level2 = 2
 普通高中 Level3 = 3
 技工学校 Level4 = 4
 职业教育 Level5 = 5
 职业高中 Level6 = 6
 中等专科 Level7 = 7
 大学专科 Level8 = 8
 大学本科 Level9 = 9
 硕士研究生 Level10 = 10
 博士研究生 Level11 = 11
","schema":{"jsonSchema":{"enum":[0,1,2,3,4,5,6,7,8,9,10,11],"type":"integer","description":"文化程度枚举
 其他 Level0 = 0
 小学 Level1 = 1
 初中 Level2 = 2
 普通高中 Level3 = 3
 技工学校 Level4 = 4
 职业教育 Level5 = 5
 职业高中 Level6 = 6
 中等专科 Level7 = 7
 大学专科 Level8 = 8
 大学本科 Level9 = 9
 硕士研究生 Level10 = 10
 博士研究生 Level11 = 11
","format":"int32"}}},{"name":"DataItem","displayName":"","id":"#/definitions/84275268","description":"","schema":{"jsonSchema":{"type":"object","properties":{"value":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["value"]}}},{"name":"DataScopeEnum","displayName":"","id":"#/definitions/84275269","description":"角色数据范围枚举
 全部数据 All = 1
 本部门及以下数据 DeptChild = 2
 本部门数据 Dept = 3
 仅本人数据 Self = 4
 自定义数据 Define = 5
","schema":{"jsonSchema":{"enum":[1,2,3,4,5],"type":"integer","description":"角色数据范围枚举
 全部数据 All = 1
 本部门及以下数据 DeptChild = 2
 本部门数据 Dept = 3
 仅本人数据 Self = 4
 自定义数据 Define = 5
","format":"int32"}}},{"name":"DatabaseOutput","displayName":"","id":"#/definitions/84275270","description":"数据库","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"],"description":"库定位器名"},"dbType":{"$ref":"#/definitions/84275276"},"connectionString":{"type":["string","null"],"description":"数据库连接字符串"}},"additionalProperties":false,"description":"数据库","x-apifox-orders":["configId","dbType","connectionString"]}}},{"name":"DbColumnInput","displayName":"","id":"#/definitions/84275271","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]},"dbColumnName":{"type":["string","null"]},"dataType":{"type":["string","null"]},"length":{"type":"integer","format":"int32"},"columnDescription":{"type":["string","null"]},"isNullable":{"type":"integer","format":"int32"},"isIdentity":{"type":"integer","format":"int32"},"isPrimarykey":{"type":"integer","format":"int32"},"decimalDigits":{"type":"integer","format":"int32"}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","dbColumnName","dataType","length","columnDescription","isNullable","isIdentity","isPrimarykey","decimalDigits"]}}},{"name":"DbColumnOutput","displayName":"","id":"#/definitions/84275272","description":"","schema":{"jsonSchema":{"type":"object","properties":{"tableName":{"type":["string","null"]},"tableId":{"type":"integer","format":"int32"},"dbColumnName":{"type":["string","null"]},"propertyName":{"type":["string","null"]},"dataType":{"type":["string","null"]},"propertyType":{"additionalProperties":false,"type":"null"},"length":{"type":"integer","format":"int32"},"columnDescription":{"type":["string","null"]},"defaultValue":{"type":["string","null"]},"isNullable":{"type":"boolean"},"isIdentity":{"type":"boolean"},"isPrimarykey":{"type":"boolean"},"value":{"additionalProperties":false,"type":"null"},"decimalDigits":{"type":"integer","format":"int32"},"scale":{"type":"integer","format":"int32"},"isArray":{"type":"boolean"},"isJson":{"type":"boolean"},"isUnsigned":{"type":["boolean","null"]},"createTableFieldSort":{"type":"integer","format":"int32"}},"additionalProperties":false,"x-apifox-orders":["tableName","tableId","dbColumnName","propertyName","dataType","propertyType","length","columnDescription","defaultValue","isNullable","isIdentity","isPrimarykey","value","decimalDigits","scale","isArray","isJson","isUnsigned","createTableFieldSort"]}}},{"name":"DbObjectType","displayName":"","id":"#/definitions/84275273","description":"","schema":{"jsonSchema":{"enum":[0,1,2],"type":"integer","format":"int32"}}},{"name":"DbTableInfo","displayName":"","id":"#/definitions/84275274","description":"","schema":{"jsonSchema":{"type":"object","properties":{"name":{"type":["string","null"]},"description":{"type":["string","null"]},"dbObjectType":{"$ref":"#/definitions/84275273"}},"additionalProperties":false,"x-apifox-orders":["name","description","dbObjectType"]}}},{"name":"DbTableInput","displayName":"","id":"#/definitions/84275275","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]},"description":{"type":["string","null"]},"dbColumnInfoList":{"type":["array","null"],"items":{"$ref":"#/definitions/84275271"}}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","description","dbColumnInfoList"]}}},{"name":"DbType","displayName":"","id":"#/definitions/84275276","description":"","schema":{"jsonSchema":{"enum":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,900],"type":"integer","format":"int32"}}},{"name":"DeleteCodeGenInput","displayName":"","id":"#/definitions/84275277","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"代码生成器Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteConfigInput","displayName":"","id":"#/definitions/84275278","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteDbColumnInput","displayName":"","id":"#/definitions/84275279","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]},"dbColumnName":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","dbColumnName"]}}},{"name":"DeleteDbTableInput","displayName":"","id":"#/definitions/84275280","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["configId","tableName"]}}},{"name":"DeleteDictDataInput","displayName":"","id":"#/definitions/84275281","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteDictTypeInput","displayName":"","id":"#/definitions/84275282","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteFileInput","displayName":"","id":"#/definitions/84275283","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteJobDetailInput","displayName":"","id":"#/definitions/84275284","description":"","schema":{"jsonSchema":{"type":"object","properties":{"jobId":{"type":["string","null"],"description":"作业Id"}},"additionalProperties":false,"x-apifox-orders":["jobId"]}}},{"name":"DeleteJobTriggerInput","displayName":"","id":"#/definitions/84275285","description":"","schema":{"jsonSchema":{"type":"object","properties":{"jobId":{"type":["string","null"],"description":"作业Id"},"triggerId":{"type":["string","null"],"description":"触发器Id"}},"additionalProperties":false,"x-apifox-orders":["jobId","triggerId"]}}},{"name":"DeleteMenuInput","displayName":"","id":"#/definitions/84275286","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteMessageTemplateInput","displayName":"","id":"#/definitions/84275287","description":"删除消息模板","schema":{"jsonSchema":{"required":["templateId"],"type":"object","properties":{"templateId":{"minLength":1,"type":"string","description":"订阅模板Id"}},"additionalProperties":false,"description":"删除消息模板","x-apifox-orders":["templateId"]}}},{"name":"DeleteNoticeInput","displayName":"","id":"#/definitions/84275288","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteOpenAccessInput","displayName":"","id":"#/definitions/84275289","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteOrgInput","displayName":"","id":"#/definitions/84275290","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeletePluginInput","displayName":"","id":"#/definitions/84275291","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeletePosInput","displayName":"","id":"#/definitions/84275292","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeletePrintInput","displayName":"","id":"#/definitions/84275293","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteRegionInput","displayName":"","id":"#/definitions/84275294","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteRoleInput","displayName":"","id":"#/definitions/84275295","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteTenantInput","displayName":"","id":"#/definitions/84275296","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DeleteUserInput","displayName":"","id":"#/definitions/84275297","description":"删除用户输入参数","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"orgId":{"type":"integer","description":"机构Id","format":"int64"}},"additionalProperties":false,"description":"删除用户输入参数","x-apifox-orders":["id","orgId"]}}},{"name":"DeleteWechatUserInput","displayName":"","id":"#/definitions/84275298","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"DictDataInput","displayName":"","id":"#/definitions/84275299","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","status"]}}},{"name":"DictTypeInput","displayName":"","id":"#/definitions/84275300","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","status"]}}},{"name":"EnumEntity","displayName":"","id":"#/definitions/84275301","description":"枚举实体","schema":{"jsonSchema":{"type":"object","properties":{"describe":{"type":["string","null"],"description":"枚举的描述"},"name":{"type":["string","null"],"description":"枚举名称"},"value":{"type":"integer","description":"枚举对象的值","format":"int32"}},"additionalProperties":false,"description":"枚举实体","x-apifox-orders":["describe","name","value"]}}},{"name":"EnumTypeOutput","displayName":"","id":"#/definitions/84275302","description":"枚举类型输出参数","schema":{"jsonSchema":{"type":"object","properties":{"typeDescribe":{"type":["string","null"],"description":"枚举类型描述"},"typeName":{"type":["string","null"],"description":"枚举类型名称"},"typeRemark":{"type":["string","null"],"description":"枚举类型备注"}},"additionalProperties":false,"description":"枚举类型输出参数","x-apifox-orders":["typeDescribe","typeName","typeRemark"]}}},{"name":"FileInput","displayName":"","id":"#/definitions/84275303","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"fileName":{"type":["string","null"],"description":"文件名称"},"url":{"type":["string","null"],"description":"文件Url"}},"additionalProperties":false,"x-apifox-orders":["id","fileName","url"]}}},{"name":"GenAuthUrlInput","displayName":"","id":"#/definitions/84275304","description":"生成网页授权Url","schema":{"jsonSchema":{"type":"object","properties":{"redirectUrl":{"type":["string","null"],"description":"RedirectUrl"},"scope":{"type":["string","null"],"description":"Scope"}},"additionalProperties":false,"description":"生成网页授权Url","x-apifox-orders":["redirectUrl","scope"]}}},{"name":"GenderEnum","displayName":"","id":"#/definitions/84275305","description":"性别枚举
 男 Male = 1
 女 Female = 2
 其他 Other = 3
","schema":{"jsonSchema":{"enum":[1,2,3],"type":"integer","description":"性别枚举
 男 Male = 1
 女 Female = 2
 其他 Other = 3
","format":"int32"}}},{"name":"IActionResult","displayName":"","id":"#/definitions/84275306","description":"","schema":{"jsonSchema":{"type":"object","additionalProperties":false,"x-apifox-orders":[]}}},{"name":"JToken","displayName":"","id":"#/definitions/84275307","description":"","schema":{"jsonSchema":{"type":"array","items":{"$ref":"#/definitions/84275307"}}}},{"name":"JobCreateTypeEnum","displayName":"","id":"#/definitions/84275308","description":"作业创建类型枚举
 内置 BuiltIn = 0
 脚本 Script = 1
 HTTP请求 Http = 2
","schema":{"jsonSchema":{"enum":[0,1,2],"type":"integer","description":"作业创建类型枚举
 内置 BuiltIn = 0
 脚本 Script = 1
 HTTP请求 Http = 2
","format":"int32"}}},{"name":"JobDetailInput","displayName":"","id":"#/definitions/84275309","description":"","schema":{"jsonSchema":{"type":"object","properties":{"jobId":{"type":["string","null"],"description":"作业Id"}},"additionalProperties":false,"x-apifox-orders":["jobId"]}}},{"name":"JobDetailOutput","displayName":"","id":"#/definitions/84275310","description":"","schema":{"jsonSchema":{"type":"object","properties":{"jobDetail":{"$ref":"#/definitions/84275383"},"jobTriggers":{"type":["array","null"],"items":{"$ref":"#/definitions/84275384"},"description":"触发器集合"}},"additionalProperties":false,"x-apifox-orders":["jobDetail","jobTriggers"]}}},{"name":"JobTriggerInput","displayName":"","id":"#/definitions/84275311","description":"","schema":{"jsonSchema":{"type":"object","properties":{"jobId":{"type":["string","null"],"description":"作业Id"},"triggerId":{"type":["string","null"],"description":"触发器Id"}},"additionalProperties":false,"x-apifox-orders":["jobId","triggerId"]}}},{"name":"LogInput","displayName":"","id":"#/definitions/84275312","description":"","schema":{"jsonSchema":{"type":"object","properties":{"startTime":{"type":["string","null"],"description":"开始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"}},"additionalProperties":false,"x-apifox-orders":["startTime","endTime"]}}},{"name":"LogLevel","displayName":"","id":"#/definitions/84275313","description":"","schema":{"jsonSchema":{"enum":[0,1,2,3,4,5,6],"type":"integer","format":"int32"}}},{"name":"LoginInput","displayName":"","id":"#/definitions/84275314","description":"用户登录参数","schema":{"jsonSchema":{"required":["account","password"],"type":"object","properties":{"account":{"minLength":2,"type":"string","description":"账号","examples":["admin"]},"password":{"minLength":3,"type":"string","description":"密码","examples":["123456"]},"codeId":{"type":"integer","description":"验证码Id","format":"int64"},"code":{"type":["string","null"],"description":"验证码"}},"additionalProperties":false,"description":"用户登录参数","x-apifox-orders":["account","password","codeId","code"]}}},{"name":"LoginOutput","displayName":"","id":"#/definitions/84275315","description":"用户登录结果","schema":{"jsonSchema":{"type":"object","properties":{"accessToken":{"type":["string","null"],"description":"令牌Token"},"refreshToken":{"type":["string","null"],"description":"刷新Token"}},"additionalProperties":false,"description":"用户登录结果","x-apifox-orders":["accessToken","refreshToken"]}}},{"name":"LoginPhoneInput","displayName":"","id":"#/definitions/84275316","description":"","schema":{"jsonSchema":{"required":["code","phone"],"type":"object","properties":{"phone":{"minLength":1,"type":"string","description":"手机号码","examples":["admin"]},"code":{"minLength":4,"type":"string","description":"验证码","examples":["123456"]}},"additionalProperties":false,"x-apifox-orders":["phone","code"]}}},{"name":"LoginUserOutput","displayName":"","id":"#/definitions/84275317","description":"用户登录信息","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"用户id","format":"int64"},"account":{"type":["string","null"],"description":"账号名称"},"realName":{"type":["string","null"],"description":"真实姓名"},"phone":{"type":["string","null"],"description":"电话"},"idCardNum":{"type":["string","null"],"description":"身份证"},"email":{"type":["string","null"],"description":"邮箱"},"accountType":{"$ref":"#/definitions/84275167"},"avatar":{"type":["string","null"],"description":"头像"},"introduction":{"type":["string","null"],"description":"个人简介"},"address":{"type":["string","null"],"description":"地址"},"signature":{"type":["string","null"],"description":"电子签名"},"orgId":{"type":"integer","description":"机构Id","format":"int64"},"orgName":{"type":["string","null"],"description":"机构名称"},"orgType":{"type":["string","null"],"description":"机构类型"},"posName":{"type":["string","null"],"description":"职位名称"},"buttons":{"type":["array","null"],"items":{"type":"string"},"description":"按钮权限集合"},"roleIds":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"角色集合"}},"additionalProperties":false,"description":"用户登录信息","x-apifox-orders":["id","account","realName","phone","idCardNum","email","accountType","avatar","introduction","address","signature","orgId","orgName","orgType","posName","buttons","roleIds"]}}},{"name":"MenuOutput","displayName":"","id":"#/definitions/84275318","description":"系统菜单返回结果","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"type":{"$ref":"#/definitions/84275319"},"name":{"type":["string","null"],"description":"名称"},"path":{"type":["string","null"],"description":"路由地址"},"component":{"type":["string","null"],"description":"组件路径"},"permission":{"type":["string","null"],"description":"权限标识"},"redirect":{"type":["string","null"],"description":"重定向"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"type":["string","null"],"description":"备注"},"createTime":{"type":"string","description":"创建时间","format":"date-time"},"updateTime":{"type":"string","description":"更新时间","format":"date-time"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"meta":{"$ref":"#/definitions/84275391"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275318"},"description":"菜单子项"}},"additionalProperties":false,"description":"系统菜单返回结果","x-apifox-orders":["id","pid","type","name","path","component","permission","redirect","orderNo","status","remark","createTime","updateTime","createUserName","updateUserName","meta","children"]}}},{"name":"MenuTypeEnum","displayName":"","id":"#/definitions/84275319","description":"系统菜单类型枚举
 目录 Dir = 1
 菜单 Menu = 2
 按钮 Btn = 3
","schema":{"jsonSchema":{"enum":[1,2,3],"type":"integer","description":"系统菜单类型枚举
 目录 Dir = 1
 菜单 Menu = 2
 按钮 Btn = 3
","format":"int32"}}},{"name":"MessageInput","displayName":"","id":"#/definitions/84275320","description":"","schema":{"jsonSchema":{"type":"object","properties":{"userId":{"type":"integer","description":"用户ID","format":"int64"},"userIds":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"用户ID列表"},"title":{"type":["string","null"],"description":"消息标题"},"messageType":{"$ref":"#/definitions/84275322"},"message":{"type":["string","null"],"description":"消息内容"}},"additionalProperties":false,"x-apifox-orders":["userId","userIds","title","messageType","message"]}}},{"name":"MessageTemplateSendInput","displayName":"","id":"#/definitions/84275321","description":"获取消息模板列表","schema":{"jsonSchema":{"required":["data","templateId","toUserOpenId"],"type":"object","properties":{"templateId":{"minLength":1,"type":"string","description":"订阅模板Id"},"toUserOpenId":{"minLength":1,"type":"string","description":"接收者的OpenId"},"data":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275268"},"description":"模板数据,格式形如 { \"key1\": { \"value\": any }, \"key2\": { \"value\": any } }","x-apifox-orders":[]},"url":{"type":["string","null"],"description":"模板跳转链接"},"miniProgramPagePath":{"type":["string","null"],"description":"所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar)"}},"additionalProperties":false,"description":"获取消息模板列表","x-apifox-orders":["templateId","toUserOpenId","data","url","miniProgramPagePath"]}}},{"name":"MessageTypeEnum","displayName":"","id":"#/definitions/84275322","description":"消息类型枚举
 消息 Info = 0
 成功 Success = 1
 警告 Warning = 2
 错误 Error = 3
","schema":{"jsonSchema":{"enum":[0,1,2,3],"type":"integer","description":"消息类型枚举
 消息 Info = 0
 成功 Success = 1
 警告 Warning = 2
 错误 Error = 3
","format":"int32"}}},{"name":"NoticeInput","displayName":"","id":"#/definitions/84275323","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id"]}}},{"name":"NoticeStatusEnum","displayName":"","id":"#/definitions/84275324","description":"通知公告状态枚举
 草稿 DRAFT = 0
 发布 PUBLIC = 1
 撤回 CANCEL = 2
 删除 DELETED = 3
","schema":{"jsonSchema":{"enum":[0,1,2,3],"type":"integer","description":"通知公告状态枚举
 草稿 DRAFT = 0
 发布 PUBLIC = 1
 撤回 CANCEL = 2
 删除 DELETED = 3
","format":"int32"}}},{"name":"NoticeTypeEnum","displayName":"","id":"#/definitions/84275325","description":"通知公告状类型枚举
 通知 NOTICE = 1
 公告 ANNOUNCEMENT = 2
","schema":{"jsonSchema":{"enum":[1,2],"type":"integer","description":"通知公告状类型枚举
 通知 NOTICE = 1
 公告 ANNOUNCEMENT = 2
","format":"int32"}}},{"name":"NoticeUserStatusEnum","displayName":"","id":"#/definitions/84275326","description":"通知公告用户状态枚举
 未读 UNREAD = 0
 已读 READ = 1
","schema":{"jsonSchema":{"enum":[0,1],"type":"integer","description":"通知公告用户状态枚举
 未读 UNREAD = 0
 已读 READ = 1
","format":"int32"}}},{"name":"OpenAccessInput","displayName":"","id":"#/definitions/84275327","description":"开放接口身份输入参数","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"accessKey":{"type":["string","null"],"description":"身份标识"}},"additionalProperties":false,"description":"开放接口身份输入参数","x-apifox-orders":["page","pageSize","field","order","descStr","accessKey"]}}},{"name":"OpenAccessOutput","displayName":"","id":"#/definitions/84275328","description":"","schema":{"jsonSchema":{"required":["accessKey","accessSecret"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"accessKey":{"maxLength":128,"minLength":1,"type":"string","description":"身份标识"},"accessSecret":{"maxLength":256,"minLength":1,"type":"string","description":"密钥"},"bindTenantId":{"type":"integer","description":"绑定租户Id","format":"int64"},"bindUserId":{"type":"integer","description":"绑定用户Id","format":"int64"},"bindUserAccount":{"type":["string","null"],"description":"绑定用户账号"},"bindTenantName":{"type":["string","null"],"description":"绑定租户名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","accessKey","accessSecret","bindTenantId","bindUserId","bindUserAccount","bindTenantName"]}}},{"name":"PageConfigInput","displayName":"","id":"#/definitions/84275329","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"},"groupCode":{"type":["string","null"],"description":"分组编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","code","groupCode"]}}},{"name":"PageDictDataInput","displayName":"","id":"#/definitions/84275330","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"dictTypeId":{"type":"integer","description":"字典类型Id","format":"int64"},"value":{"type":["string","null"],"description":"值"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","dictTypeId","value","code"]}}},{"name":"PageDictTypeInput","displayName":"","id":"#/definitions/84275331","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","code"]}}},{"name":"PageFileInput","displayName":"","id":"#/definitions/84275332","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"fileName":{"type":["string","null"],"description":"文件名称"},"startTime":{"type":["string","null"],"description":"开始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","fileName","startTime","endTime"]}}},{"name":"PageJobDetailInput","displayName":"","id":"#/definitions/84275333","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"jobId":{"type":["string","null"],"description":"作业Id"},"description":{"type":["string","null"],"description":"描述信息"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","jobId","description"]}}},{"name":"PageJobTriggerRecordInput","displayName":"","id":"#/definitions/84275334","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"jobId":{"type":["string","null"],"description":"作业Id"},"triggerId":{"type":["string","null"],"description":"触发器Id"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","jobId","triggerId"]}}},{"name":"PageLogInput","displayName":"","id":"#/definitions/84275335","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"startTime":{"type":["string","null"],"description":"开始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","startTime","endTime"]}}},{"name":"PageNoticeInput","displayName":"","id":"#/definitions/84275336","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"title":{"type":["string","null"],"description":"标题"},"type":{"$ref":"#/definitions/84275325"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","title","type"]}}},{"name":"PageOnlineUserInput","displayName":"","id":"#/definitions/84275337","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"userName":{"type":["string","null"],"description":"账号名称"},"realName":{"type":["string","null"],"description":"真实姓名"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","userName","realName"]}}},{"name":"PagePluginInput","displayName":"","id":"#/definitions/84275338","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","code"]}}},{"name":"PagePrintInput","displayName":"","id":"#/definitions/84275339","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","code"]}}},{"name":"PageRegionInput","displayName":"","id":"#/definitions/84275340","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"pid":{"type":"integer","description":"父节点Id","format":"int64"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","pid","name","code"]}}},{"name":"PageRoleInput","displayName":"","id":"#/definitions/84275341","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","code"]}}},{"name":"PageTenantInput","displayName":"","id":"#/definitions/84275342","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"name":{"type":["string","null"],"description":"名称"},"phone":{"type":["string","null"],"description":"电话"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","name","phone"]}}},{"name":"PageUserInput","displayName":"","id":"#/definitions/84275343","description":"获取用户分页列表输入参数","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"account":{"type":["string","null"],"description":"账号"},"realName":{"type":["string","null"],"description":"姓名"},"phone":{"type":["string","null"],"description":"手机号"},"orgId":{"type":"integer","description":"查询时所选机构Id","format":"int64"}},"additionalProperties":false,"description":"获取用户分页列表输入参数","x-apifox-orders":["page","pageSize","field","order","descStr","account","realName","phone","orgId"]}}},{"name":"PlatformTypeEnum","displayName":"","id":"#/definitions/84275344","description":"平台类型枚举
 微信公众号 微信公众号 = 1
 微信小程序 微信小程序 = 2
 QQ QQ = 3
 支付宝 Alipay = 4
 Gitee Gitee = 5
","schema":{"jsonSchema":{"enum":[1,2,3,4,5],"type":"integer","description":"平台类型枚举
 微信公众号 微信公众号 = 1
 微信小程序 微信小程序 = 2
 QQ QQ = 3
 支付宝 Alipay = 4
 Gitee Gitee = 5
","format":"int32"}}},{"name":"ResetPwdUserInput","displayName":"","id":"#/definitions/84275345","description":"重置用户密码输入参数","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"description":"重置用户密码输入参数","x-apifox-orders":["id"]}}},{"name":"RoleInput","displayName":"","id":"#/definitions/84275346","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","status"]}}},{"name":"RoleMenuInput","displayName":"","id":"#/definitions/84275347","description":"授权角色菜单","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"menuIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"菜单Id集合"}},"additionalProperties":false,"description":"授权角色菜单","x-apifox-orders":["id","menuIdList"]}}},{"name":"RoleOrgInput","displayName":"","id":"#/definitions/84275348","description":"授权角色机构","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"dataScope":{"type":"integer","description":"数据范围","format":"int32"},"orgIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"机构Id集合"}},"additionalProperties":false,"description":"授权角色机构","x-apifox-orders":["id","dataScope","orgIdList"]}}},{"name":"RoleOutput","displayName":"","id":"#/definitions/84275349","description":"角色列表输出参数","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"Id","format":"int64"},"name":{"type":["string","null"],"description":"名称"},"code":{"type":["string","null"],"description":"编码"}},"additionalProperties":false,"description":"角色列表输出参数","x-apifox-orders":["id","name","code"]}}},{"name":"SendSubscribeMessageInput","displayName":"","id":"#/definitions/84275350","description":"发送订阅消息","schema":{"jsonSchema":{"required":["data","templateId","toUserOpenId"],"type":"object","properties":{"templateId":{"minLength":1,"type":"string","description":"订阅模板Id"},"toUserOpenId":{"minLength":1,"type":"string","description":"接收者的OpenId"},"data":{"type":"object","additionalProperties":{"$ref":"#/definitions/84275268"},"description":"模板内容,格式形如 { \"key1\": { \"value\": any }, \"key2\": { \"value\": any } }","x-apifox-orders":[]},"miniprogramState":{"type":["string","null"],"description":"跳转小程序类型"},"language":{"type":["string","null"],"description":"语言类型"},"miniProgramPagePath":{"type":["string","null"],"description":"点击模板卡片后的跳转页面(仅限本小程序内的页面),支持带参数(示例pages/app/index?foo=bar)"}},"additionalProperties":false,"description":"发送订阅消息","x-apifox-orders":["templateId","toUserOpenId","data","miniprogramState","language","miniProgramPagePath"]}}},{"name":"SignatureInput","displayName":"","id":"#/definitions/84275351","description":"获取配置签名","schema":{"jsonSchema":{"type":"object","properties":{"url":{"type":["string","null"],"description":"Url"}},"additionalProperties":false,"description":"获取配置签名","x-apifox-orders":["url"]}}},{"name":"SmKeyPairOutput","displayName":"","id":"#/definitions/84275352","description":"国密公钥私钥对输出","schema":{"jsonSchema":{"type":"object","properties":{"privateKey":{"type":["string","null"],"description":"私匙"},"publicKey":{"type":["string","null"],"description":"公匙"}},"additionalProperties":false,"description":"国密公钥私钥对输出","x-apifox-orders":["privateKey","publicKey"]}}},{"name":"SqlSugarPagedList_JobDetailOutput","displayName":"","id":"#/definitions/84275353","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275310"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_OpenAccessOutput","displayName":"","id":"#/definitions/84275354","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275328"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysCodeGen","displayName":"","id":"#/definitions/84275355","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275376"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysConfig","displayName":"","id":"#/definitions/84275356","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275378"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysDictData","displayName":"","id":"#/definitions/84275357","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275379"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysDictType","displayName":"","id":"#/definitions/84275358","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275380"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysFile","displayName":"","id":"#/definitions/84275359","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275381"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysJobTriggerRecord","displayName":"","id":"#/definitions/84275360","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275385"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysLogDiff","displayName":"","id":"#/definitions/84275361","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275386"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysLogEx","displayName":"","id":"#/definitions/84275362","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275387"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysLogOp","displayName":"","id":"#/definitions/84275363","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275388"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysLogVis","displayName":"","id":"#/definitions/84275364","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275389"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysNotice","displayName":"","id":"#/definitions/84275365","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275392"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysNoticeUser","displayName":"","id":"#/definitions/84275366","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275393"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysOnlineUser","displayName":"","id":"#/definitions/84275367","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275394"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysPlugin","displayName":"","id":"#/definitions/84275368","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275396"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysPrint","displayName":"","id":"#/definitions/84275369","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275398"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysRegion","displayName":"","id":"#/definitions/84275370","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275399"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysRole","displayName":"","id":"#/definitions/84275371","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275400"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_SysWechatUser","displayName":"","id":"#/definitions/84275372","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275404"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_TenantOutput","displayName":"","id":"#/definitions/84275373","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275408"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"SqlSugarPagedList_UserOutput","displayName":"","id":"#/definitions/84275374","description":"分页泛型集合","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"页码","format":"int32"},"pageSize":{"type":"integer","description":"页容量","format":"int32"},"total":{"type":"integer","description":"总条数","format":"int32"},"totalPages":{"type":"integer","description":"总页数","format":"int32"},"items":{"type":["array","null"],"items":{"$ref":"#/definitions/84275434"},"description":"当前页集合"},"hasPrevPage":{"type":"boolean","description":"是否有上一页"},"hasNextPage":{"type":"boolean","description":"是否有下一页"}},"additionalProperties":false,"description":"分页泛型集合","x-apifox-orders":["page","pageSize","total","totalPages","items","hasPrevPage","hasNextPage"]}}},{"name":"StatusEnum","displayName":"","id":"#/definitions/84275375","description":"通用状态枚举
 启用 Enable = 1
 停用 Disable = 2
","schema":{"jsonSchema":{"enum":[1,2],"type":"integer","description":"通用状态枚举
 启用 Enable = 1
 停用 Disable = 2
","format":"int32"}}},{"name":"SysCodeGen","displayName":"","id":"#/definitions/84275376","description":"代码生成表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"authorName":{"maxLength":32,"type":["string","null"],"description":"作者姓名"},"tablePrefix":{"maxLength":8,"type":["string","null"],"description":"是否移除表前缀"},"generateType":{"maxLength":32,"type":["string","null"],"description":"生成方式"},"configId":{"maxLength":64,"type":["string","null"],"description":"库定位器名"},"dbName":{"maxLength":64,"type":["string","null"],"description":"数据库名(保留字段)"},"dbType":{"maxLength":64,"type":["string","null"],"description":"数据库类型"},"connectionString":{"maxLength":256,"type":["string","null"],"description":"数据库链接"},"tableName":{"maxLength":128,"type":["string","null"],"description":"数据库表名"},"nameSpace":{"maxLength":128,"type":["string","null"],"description":"命名空间"},"busName":{"maxLength":128,"type":["string","null"],"description":"业务名"},"menuPid":{"type":"integer","description":"菜单编码","format":"int64"},"printType":{"maxLength":32,"type":["string","null"],"description":"支持打印类型"},"printName":{"maxLength":32,"type":["string","null"],"description":"打印模版名称"}},"additionalProperties":false,"description":"代码生成表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","authorName","tablePrefix","generateType","configId","dbName","dbType","connectionString","tableName","nameSpace","busName","menuPid","printType","printName"]}}},{"name":"SysCodeGenConfig","displayName":"","id":"#/definitions/84275377","description":"代码生成字段配置表","schema":{"jsonSchema":{"required":["columnName","propertyName"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"codeGenId":{"type":"integer","description":"代码生成主表Id","format":"int64"},"columnName":{"maxLength":128,"minLength":1,"type":"string","description":"数据库字段名"},"propertyName":{"maxLength":128,"minLength":1,"type":"string","description":"实体属性名"},"columnLength":{"type":"integer","description":"字段数据长度","format":"int32"},"columnComment":{"maxLength":128,"type":["string","null"],"description":"字段描述"},"netType":{"maxLength":64,"type":["string","null"],"description":".NET数据类型"},"effectType":{"maxLength":64,"type":["string","null"],"description":"作用类型(字典)"},"fkEntityName":{"maxLength":64,"type":["string","null"],"description":"外键实体名称"},"fkTableName":{"maxLength":128,"type":["string","null"],"description":"外键表名称"},"fkColumnName":{"maxLength":64,"type":["string","null"],"description":"外键显示字段"},"fkColumnNetType":{"maxLength":64,"type":["string","null"],"description":"外键显示字段.NET类型"},"dictTypeCode":{"maxLength":64,"type":["string","null"],"description":"字典编码"},"whetherRetract":{"maxLength":8,"type":["string","null"],"description":"列表是否缩进(字典)"},"whetherRequired":{"maxLength":8,"type":["string","null"],"description":"是否必填(字典)"},"whetherSortable":{"maxLength":8,"type":["string","null"],"description":"是否可排序(字典)"},"queryWhether":{"maxLength":8,"type":["string","null"],"description":"是否是查询条件"},"queryType":{"maxLength":16,"type":["string","null"],"description":"查询方式"},"whetherTable":{"maxLength":8,"type":["string","null"],"description":"列表显示"},"whetherAddUpdate":{"maxLength":8,"type":["string","null"],"description":"增改"},"columnKey":{"maxLength":8,"type":["string","null"],"description":"主键"},"dataType":{"maxLength":64,"type":["string","null"],"description":"数据库中类型(物理类型)"},"whetherCommon":{"maxLength":8,"type":["string","null"],"description":"是否通用字段"},"displayColumn":{"type":["string","null"],"description":"显示文本字段"},"valueColumn":{"maxLength":128,"type":["string","null"],"description":"选中值字段"},"pidColumn":{"maxLength":128,"type":["string","null"],"description":"父级字段"},"orderNo":{"type":"integer","description":"排序","format":"int32"}},"additionalProperties":false,"description":"代码生成字段配置表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","codeGenId","columnName","propertyName","columnLength","columnComment","netType","effectType","fkEntityName","fkTableName","fkColumnName","fkColumnNetType","dictTypeCode","whetherRetract","whetherRequired","whetherSortable","queryWhether","queryType","whetherTable","whetherAddUpdate","columnKey","dataType","whetherCommon","displayColumn","valueColumn","pidColumn","orderNo"]}}},{"name":"SysConfig","displayName":"","id":"#/definitions/84275378","description":"系统参数配置表","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"value":{"maxLength":64,"type":["string","null"],"description":"属性值"},"sysFlag":{"$ref":"#/definitions/84275444"},"groupCode":{"maxLength":64,"type":["string","null"],"description":"分组编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"}},"additionalProperties":false,"description":"系统参数配置表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","value","sysFlag","groupCode","orderNo","remark"]}}},{"name":"SysDictData","displayName":"","id":"#/definitions/84275379","description":"系统字典值表","schema":{"jsonSchema":{"required":["code","value"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"dictTypeId":{"type":"integer","description":"字典类型Id","format":"int64"},"value":{"maxLength":128,"minLength":1,"type":"string","description":"值"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"tagType":{"maxLength":16,"type":["string","null"],"description":"显示样式-标签颜色"},"styleSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Style(控制显示样式)"},"classSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Class(控制显示样式)"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":2048,"type":["string","null"],"description":"备注"},"extData":{"type":["string","null"],"description":"拓展数据(保存业务功能的配置项)"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"description":"系统字典值表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","dictTypeId","value","code","tagType","styleSetting","classSetting","orderNo","remark","extData","status"]}}},{"name":"SysDictType","displayName":"","id":"#/definitions/84275380","description":"系统字典类型表","schema":{"jsonSchema":{"required":["code","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275379"},"description":"字典值集合"}},"additionalProperties":false,"description":"系统字典类型表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","orderNo","remark","status","children"]}}},{"name":"SysFile","displayName":"","id":"#/definitions/84275381","description":"系统文件表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"provider":{"maxLength":128,"type":["string","null"],"description":"提供者"},"bucketName":{"maxLength":128,"type":["string","null"],"description":"仓储名称"},"fileName":{"maxLength":128,"type":["string","null"],"description":"文件名称(源文件名)"},"suffix":{"maxLength":16,"type":["string","null"],"description":"文件后缀"},"filePath":{"maxLength":128,"type":["string","null"],"description":"存储路径"},"sizeKb":{"maxLength":16,"type":["string","null"],"description":"文件大小KB"},"sizeInfo":{"maxLength":64,"type":["string","null"],"description":"文件大小信息-计算后的"},"url":{"maxLength":512,"type":["string","null"],"description":"外链地址-OSS上传后生成外链地址方便前端预览"},"fileMd5":{"maxLength":128,"type":["string","null"],"description":"文件MD5"}},"additionalProperties":false,"description":"系统文件表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","provider","bucketName","fileName","suffix","filePath","sizeKb","sizeInfo","url","fileMd5"]}}},{"name":"SysJobCluster","displayName":"","id":"#/definitions/84275382","description":"系统作业集群表","schema":{"jsonSchema":{"required":["clusterId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"clusterId":{"maxLength":64,"minLength":1,"type":"string","description":"作业集群Id"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"status":{"$ref":"#/definitions/84275260"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"}},"additionalProperties":false,"description":"系统作业集群表","x-apifox-orders":["id","clusterId","description","status","updatedTime"]}}},{"name":"SysJobDetail","displayName":"","id":"#/definitions/84275383","description":"系统作业信息表","schema":{"jsonSchema":{"required":["jobId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"jobId":{"maxLength":64,"minLength":1,"type":"string","description":"作业Id"},"groupName":{"maxLength":128,"type":["string","null"],"description":"组名称"},"jobType":{"maxLength":128,"type":["string","null"],"description":"作业类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"concurrent":{"type":"boolean","description":"是否并行执行"},"includeAnnotations":{"type":"boolean","description":"是否扫描特性触发器"},"properties":{"type":["string","null"],"description":"额外数据"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createType":{"$ref":"#/definitions/84275308"},"scriptCode":{"type":["string","null"],"description":"脚本代码"}},"additionalProperties":false,"description":"系统作业信息表","x-apifox-orders":["id","jobId","groupName","jobType","assemblyName","description","concurrent","includeAnnotations","properties","updatedTime","createType","scriptCode"]}}},{"name":"SysJobTrigger","displayName":"","id":"#/definitions/84275384","description":"系统作业触发器表","schema":{"jsonSchema":{"required":["jobId","triggerId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"triggerId":{"maxLength":64,"minLength":1,"type":"string","description":"触发器Id"},"jobId":{"maxLength":64,"minLength":1,"type":"string","description":"作业Id"},"triggerType":{"maxLength":128,"type":["string","null"],"description":"触发器类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"args":{"maxLength":128,"type":["string","null"],"description":"参数"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"status":{"$ref":"#/definitions/84275411"},"startTime":{"type":["string","null"],"description":"起始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"},"lastRunTime":{"type":["string","null"],"description":"最近运行时间","format":"date-time"},"nextRunTime":{"type":["string","null"],"description":"下一次运行时间","format":"date-time"},"numberOfRuns":{"type":"integer","description":"触发次数","format":"int64"},"maxNumberOfRuns":{"type":"integer","description":"最大触发次数(0:不限制,n:N次)","format":"int64"},"numberOfErrors":{"type":"integer","description":"出错次数","format":"int64"},"maxNumberOfErrors":{"type":"integer","description":"最大出错次数(0:不限制,n:N次)","format":"int64"},"numRetries":{"type":"integer","description":"重试次数","format":"int32"},"retryTimeout":{"type":"integer","description":"重试间隔时间(ms)","format":"int32"},"startNow":{"type":"boolean","description":"是否立即启动"},"runOnStart":{"type":"boolean","description":"是否启动时执行一次"},"resetOnlyOnce":{"type":"boolean","description":"是否在启动时重置最大触发次数等于一次的作业"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"}},"additionalProperties":false,"description":"系统作业触发器表","x-apifox-orders":["id","triggerId","jobId","triggerType","assemblyName","args","description","status","startTime","endTime","lastRunTime","nextRunTime","numberOfRuns","maxNumberOfRuns","numberOfErrors","maxNumberOfErrors","numRetries","retryTimeout","startNow","runOnStart","resetOnlyOnce","updatedTime"]}}},{"name":"SysJobTriggerRecord","displayName":"","id":"#/definitions/84275385","description":"系统作业触发器运行记录表","schema":{"jsonSchema":{"required":["jobId","triggerId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"jobId":{"maxLength":64,"minLength":1,"type":"string","description":"作业Id"},"triggerId":{"maxLength":64,"minLength":1,"type":"string","description":"触发器Id"},"numberOfRuns":{"type":"integer","description":"当前运行次数","format":"int64"},"lastRunTime":{"type":["string","null"],"description":"最近运行时间","format":"date-time"},"nextRunTime":{"type":["string","null"],"description":"下一次运行时间","format":"date-time"},"status":{"$ref":"#/definitions/84275411"},"result":{"maxLength":128,"type":["string","null"],"description":"本次执行结果"},"elapsedTime":{"type":"integer","description":"本次执行耗时","format":"int64"},"createdTime":{"type":["string","null"],"description":"创建时间","format":"date-time"}},"additionalProperties":false,"description":"系统作业触发器运行记录表","x-apifox-orders":["id","jobId","triggerId","numberOfRuns","lastRunTime","nextRunTime","status","result","elapsedTime","createdTime"]}}},{"name":"SysLogDiff","displayName":"","id":"#/definitions/84275386","description":"系统差异日志表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"beforeData":{"type":["string","null"],"description":"操作前记录"},"afterData":{"type":["string","null"],"description":"操作后记录"},"sql":{"type":["string","null"],"description":"Sql"},"parameters":{"type":["string","null"],"description":"参数 手动传入的参数"},"businessData":{"type":["string","null"],"description":"业务对象"},"diffType":{"type":["string","null"],"description":"差异操作"},"elapsed":{"type":["integer","null"],"description":"耗时","format":"int64"}},"additionalProperties":false,"description":"系统差异日志表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","beforeData","afterData","sql","parameters","businessData","diffType","elapsed"]}}},{"name":"SysLogEx","displayName":"","id":"#/definitions/84275387","description":"系统异常日志表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"controllerName":{"maxLength":256,"type":["string","null"],"description":"模块名称"},"actionName":{"maxLength":256,"type":["string","null"],"description":"方法名称"},"displayTitle":{"maxLength":256,"type":["string","null"],"description":"显示名称"},"status":{"maxLength":32,"type":["string","null"],"description":"执行状态"},"remoteIp":{"maxLength":256,"type":["string","null"],"description":"IP地址"},"location":{"maxLength":128,"type":["string","null"],"description":"登录地点"},"longitude":{"type":["number","null"],"description":"经度","format":"double"},"latitude":{"type":["number","null"],"description":"维度","format":"double"},"browser":{"maxLength":1024,"type":["string","null"],"description":"浏览器"},"os":{"maxLength":256,"type":["string","null"],"description":"操作系统"},"elapsed":{"type":["integer","null"],"description":"操作用时","format":"int64"},"logDateTime":{"type":["string","null"],"description":"日志时间","format":"date-time"},"logLevel":{"$ref":"#/definitions/84275313"},"account":{"maxLength":32,"type":["string","null"],"description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"},"httpMethod":{"maxLength":32,"type":["string","null"],"description":"请求方式"},"requestUrl":{"type":["string","null"],"description":"请求地址"},"requestParam":{"type":["string","null"],"description":"请求参数"},"returnResult":{"type":["string","null"],"description":"返回结果"},"eventId":{"type":["integer","null"],"description":"事件Id","format":"int32"},"threadId":{"type":["integer","null"],"description":"线程Id","format":"int32"},"traceId":{"maxLength":128,"type":["string","null"],"description":"请求跟踪Id"},"exception":{"type":["string","null"],"description":"异常信息"},"message":{"type":["string","null"],"description":"日志消息Json"}},"additionalProperties":false,"description":"系统异常日志表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","controllerName","actionName","displayTitle","status","remoteIp","location","longitude","latitude","browser","os","elapsed","logDateTime","logLevel","account","realName","httpMethod","requestUrl","requestParam","returnResult","eventId","threadId","traceId","exception","message"]}}},{"name":"SysLogOp","displayName":"","id":"#/definitions/84275388","description":"系统操作日志表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"controllerName":{"maxLength":256,"type":["string","null"],"description":"模块名称"},"actionName":{"maxLength":256,"type":["string","null"],"description":"方法名称"},"displayTitle":{"maxLength":256,"type":["string","null"],"description":"显示名称"},"status":{"maxLength":32,"type":["string","null"],"description":"执行状态"},"remoteIp":{"maxLength":256,"type":["string","null"],"description":"IP地址"},"location":{"maxLength":128,"type":["string","null"],"description":"登录地点"},"longitude":{"type":["number","null"],"description":"经度","format":"double"},"latitude":{"type":["number","null"],"description":"维度","format":"double"},"browser":{"maxLength":1024,"type":["string","null"],"description":"浏览器"},"os":{"maxLength":256,"type":["string","null"],"description":"操作系统"},"elapsed":{"type":["integer","null"],"description":"操作用时","format":"int64"},"logDateTime":{"type":["string","null"],"description":"日志时间","format":"date-time"},"logLevel":{"$ref":"#/definitions/84275313"},"account":{"maxLength":32,"type":["string","null"],"description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"},"httpMethod":{"maxLength":32,"type":["string","null"],"description":"请求方式"},"requestUrl":{"type":["string","null"],"description":"请求地址"},"requestParam":{"type":["string","null"],"description":"请求参数"},"returnResult":{"type":["string","null"],"description":"返回结果"},"eventId":{"type":["integer","null"],"description":"事件Id","format":"int32"},"threadId":{"type":["integer","null"],"description":"线程Id","format":"int32"},"traceId":{"maxLength":128,"type":["string","null"],"description":"请求跟踪Id"},"exception":{"type":["string","null"],"description":"异常信息"},"message":{"type":["string","null"],"description":"日志消息Json"}},"additionalProperties":false,"description":"系统操作日志表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","controllerName","actionName","displayTitle","status","remoteIp","location","longitude","latitude","browser","os","elapsed","logDateTime","logLevel","account","realName","httpMethod","requestUrl","requestParam","returnResult","eventId","threadId","traceId","exception","message"]}}},{"name":"SysLogVis","displayName":"","id":"#/definitions/84275389","description":"系统访问日志表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"controllerName":{"maxLength":256,"type":["string","null"],"description":"模块名称"},"actionName":{"maxLength":256,"type":["string","null"],"description":"方法名称"},"displayTitle":{"maxLength":256,"type":["string","null"],"description":"显示名称"},"status":{"maxLength":32,"type":["string","null"],"description":"执行状态"},"remoteIp":{"maxLength":256,"type":["string","null"],"description":"IP地址"},"location":{"maxLength":128,"type":["string","null"],"description":"登录地点"},"longitude":{"type":["number","null"],"description":"经度","format":"double"},"latitude":{"type":["number","null"],"description":"维度","format":"double"},"browser":{"maxLength":1024,"type":["string","null"],"description":"浏览器"},"os":{"maxLength":256,"type":["string","null"],"description":"操作系统"},"elapsed":{"type":["integer","null"],"description":"操作用时","format":"int64"},"logDateTime":{"type":["string","null"],"description":"日志时间","format":"date-time"},"logLevel":{"$ref":"#/definitions/84275313"},"account":{"maxLength":32,"type":["string","null"],"description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"}},"additionalProperties":false,"description":"系统访问日志表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","controllerName","actionName","displayTitle","status","remoteIp","location","longitude","latitude","browser","os","elapsed","logDateTime","logLevel","account","realName"]}}},{"name":"SysMenu","displayName":"","id":"#/definitions/84275390","description":"系统菜单表","schema":{"jsonSchema":{"required":["title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"pid":{"type":"integer","description":"父Id","format":"int64"},"type":{"$ref":"#/definitions/84275319"},"name":{"maxLength":64,"type":["string","null"],"description":"路由名称"},"path":{"maxLength":128,"type":["string","null"],"description":"路由地址"},"component":{"maxLength":128,"type":["string","null"],"description":"组件路径"},"redirect":{"maxLength":128,"type":["string","null"],"description":"重定向"},"permission":{"maxLength":128,"type":["string","null"],"description":"权限标识"},"title":{"maxLength":64,"minLength":1,"type":"string","description":"菜单名称"},"icon":{"maxLength":128,"type":["string","null"],"description":"图标"},"isIframe":{"type":"boolean","description":"是否内嵌"},"outLink":{"maxLength":256,"type":["string","null"],"description":"外链链接"},"isHide":{"type":"boolean","description":"是否隐藏"},"isKeepAlive":{"type":"boolean","description":"是否缓存"},"isAffix":{"type":"boolean","description":"是否固定"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275390"},"description":"菜单子项"}},"additionalProperties":false,"description":"系统菜单表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","pid","type","name","path","component","redirect","permission","title","icon","isIframe","outLink","isHide","isKeepAlive","isAffix","orderNo","status","remark","children"]}}},{"name":"SysMenuMeta","displayName":"","id":"#/definitions/84275391","description":"菜单Meta配置","schema":{"jsonSchema":{"type":"object","properties":{"title":{"type":["string","null"],"description":"标题"},"icon":{"type":["string","null"],"description":"图标"},"isIframe":{"type":"boolean","description":"是否内嵌"},"isLink":{"type":["string","null"],"description":"外链链接"},"isHide":{"type":"boolean","description":"是否隐藏"},"isKeepAlive":{"type":"boolean","description":"是否缓存"},"isAffix":{"type":"boolean","description":"是否固定"}},"additionalProperties":false,"description":"菜单Meta配置","x-apifox-orders":["title","icon","isIframe","isLink","isHide","isKeepAlive","isAffix"]}}},{"name":"SysNotice","displayName":"","id":"#/definitions/84275392","description":"系统通知公告表","schema":{"jsonSchema":{"required":["content","title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"title":{"maxLength":32,"minLength":1,"type":"string","description":"标题"},"content":{"minLength":1,"type":"string","description":"内容"},"type":{"$ref":"#/definitions/84275325"},"publicUserId":{"type":"integer","description":"发布人Id","format":"int64"},"publicUserName":{"maxLength":32,"type":["string","null"],"description":"发布人姓名"},"publicOrgId":{"type":"integer","description":"发布机构Id","format":"int64"},"publicOrgName":{"maxLength":64,"type":["string","null"],"description":"发布机构名称"},"publicTime":{"type":["string","null"],"description":"发布时间","format":"date-time"},"cancelTime":{"type":["string","null"],"description":"撤回时间","format":"date-time"},"status":{"$ref":"#/definitions/84275324"}},"additionalProperties":false,"description":"系统通知公告表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","title","content","type","publicUserId","publicUserName","publicOrgId","publicOrgName","publicTime","cancelTime","status"]}}},{"name":"SysNoticeUser","displayName":"","id":"#/definitions/84275393","description":"系统通知公告用户表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"noticeId":{"type":"integer","description":"通知公告Id","format":"int64"},"sysNotice":{"$ref":"#/definitions/84275392"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"readTime":{"type":["string","null"],"description":"阅读时间","format":"date-time"},"readStatus":{"$ref":"#/definitions/84275326"}},"additionalProperties":false,"description":"系统通知公告用户表","x-apifox-orders":["id","noticeId","sysNotice","userId","readTime","readStatus"]}}},{"name":"SysOnlineUser","displayName":"","id":"#/definitions/84275394","description":"系统在线用户表","schema":{"jsonSchema":{"required":["userName"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"connectionId":{"type":["string","null"],"description":"连接Id"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"userName":{"maxLength":32,"minLength":1,"type":"string","description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"},"time":{"type":["string","null"],"description":"连接时间","format":"date-time"},"ip":{"maxLength":256,"type":["string","null"],"description":"连接IP"},"browser":{"maxLength":128,"type":["string","null"],"description":"浏览器"},"os":{"maxLength":128,"type":["string","null"],"description":"操作系统"}},"additionalProperties":false,"description":"系统在线用户表","x-apifox-orders":["id","tenantId","connectionId","userId","userName","realName","time","ip","browser","os"]}}},{"name":"SysOrg","displayName":"","id":"#/definitions/84275395","description":"系统机构表","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"level":{"type":["integer","null"],"description":"级别","format":"int32"},"type":{"maxLength":64,"type":["string","null"],"description":"机构类型-数据字典"},"directorId":{"type":["integer","null"],"description":"负责人Id","format":"int64"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275395"},"description":"机构子项"},"disabled":{"type":"boolean","description":"是否禁止选中"}},"additionalProperties":false,"description":"系统机构表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","pid","name","code","level","type","directorId","orderNo","status","remark","children","disabled"]}}},{"name":"SysPlugin","displayName":"","id":"#/definitions/84275396","description":"系统动态插件表","schema":{"jsonSchema":{"required":["csharpCode","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"csharpCode":{"minLength":1,"type":"string","description":"C#代码"},"assemblyName":{"maxLength":512,"type":["string","null"],"description":"程序集名称"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"}},"additionalProperties":false,"description":"系统动态插件表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","name","csharpCode","assemblyName","orderNo","status","remark"]}}},{"name":"SysPos","displayName":"","id":"#/definitions/84275397","description":"系统职位表","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"description":"系统职位表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","name","code","orderNo","remark","status"]}}},{"name":"SysPrint","displayName":"","id":"#/definitions/84275398","description":"系统打印模板表","schema":{"jsonSchema":{"required":["name","template"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"template":{"minLength":1,"type":"string","description":"打印模板"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"}},"additionalProperties":false,"description":"系统打印模板表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","name","template","orderNo","status","remark"]}}},{"name":"SysRegion","displayName":"","id":"#/definitions/84275399","description":"系统行政地区表","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"name":{"maxLength":128,"minLength":1,"type":"string","description":"名称"},"shortName":{"maxLength":32,"type":["string","null"],"description":"简称"},"mergerName":{"maxLength":64,"type":["string","null"],"description":"组合名"},"code":{"maxLength":32,"type":["string","null"],"description":"行政代码"},"zipCode":{"maxLength":6,"type":["string","null"],"description":"邮政编码"},"cityCode":{"maxLength":6,"type":["string","null"],"description":"区号"},"level":{"type":"integer","description":"层级","format":"int32"},"pinYin":{"maxLength":128,"type":["string","null"],"description":"拼音"},"lng":{"type":"number","description":"经度","format":"float"},"lat":{"type":"number","description":"维度","format":"float"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275399"},"description":"机构子项"}},"additionalProperties":false,"description":"系统行政地区表","x-apifox-orders":["id","pid","name","shortName","mergerName","code","zipCode","cityCode","level","pinYin","lng","lat","orderNo","remark","children"]}}},{"name":"SysRole","displayName":"","id":"#/definitions/84275400","description":"系统角色表","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"dataScope":{"$ref":"#/definitions/84275269"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"description":"系统角色表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","name","code","orderNo","dataScope","remark","status"]}}},{"name":"SysUser","displayName":"","id":"#/definitions/84275401","description":"系统用户表","schema":{"jsonSchema":{"required":["account"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"account":{"maxLength":32,"minLength":1,"type":"string","description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"},"nickName":{"maxLength":32,"type":["string","null"],"description":"昵称"},"avatar":{"maxLength":512,"type":["string","null"],"description":"头像"},"sex":{"$ref":"#/definitions/84275305"},"age":{"type":"integer","description":"年龄","format":"int32"},"birthday":{"type":["string","null"],"description":"出生日期","format":"date-time"},"nation":{"maxLength":32,"type":["string","null"],"description":"民族"},"phone":{"maxLength":16,"type":["string","null"],"description":"手机号码"},"cardType":{"$ref":"#/definitions/84275258"},"idCardNum":{"maxLength":32,"type":["string","null"],"description":"身份证号"},"email":{"maxLength":64,"type":["string","null"],"description":"邮箱"},"address":{"maxLength":256,"type":["string","null"],"description":"地址"},"cultureLevel":{"$ref":"#/definitions/84275267"},"politicalOutlook":{"maxLength":16,"type":["string","null"],"description":"政治面貌"},"college":{"maxLength":128,"type":["string","null"],"description":"毕业院校"},"officePhone":{"maxLength":16,"type":["string","null"],"description":"办公电话"},"emergencyContact":{"maxLength":32,"type":["string","null"],"description":"紧急联系人"},"emergencyPhone":{"maxLength":16,"type":["string","null"],"description":"紧急联系人电话"},"emergencyAddress":{"maxLength":256,"type":["string","null"],"description":"紧急联系人地址"},"introduction":{"maxLength":512,"type":["string","null"],"description":"个人简介"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"accountType":{"$ref":"#/definitions/84275167"},"orgId":{"type":"integer","description":"直属机构Id","format":"int64"},"sysOrg":{"$ref":"#/definitions/84275395"},"managerUserId":{"type":["integer","null"],"description":"直属主管Id","format":"int64"},"posId":{"type":"integer","description":"职位Id","format":"int64"},"jobNum":{"maxLength":32,"type":["string","null"],"description":"工号"},"posLevel":{"maxLength":32,"type":["string","null"],"description":"职级"},"posTitle":{"maxLength":32,"type":["string","null"],"description":"职称"},"expertise":{"maxLength":32,"type":["string","null"],"description":"擅长领域"},"officeZone":{"maxLength":32,"type":["string","null"],"description":"办公区域"},"office":{"maxLength":32,"type":["string","null"],"description":"办公室"},"joinDate":{"type":["string","null"],"description":"入职日期","format":"date-time"},"lastLoginIp":{"maxLength":256,"type":["string","null"],"description":"最新登录Ip"},"lastLoginAddress":{"maxLength":128,"type":["string","null"],"description":"最新登录地点"},"lastLoginTime":{"type":["string","null"],"description":"最新登录时间","format":"date-time"},"lastLoginDevice":{"maxLength":128,"type":["string","null"],"description":"最新登录设备"},"signature":{"maxLength":512,"type":["string","null"],"description":"电子签名"}},"additionalProperties":false,"description":"系统用户表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","account","realName","nickName","avatar","sex","age","birthday","nation","phone","cardType","idCardNum","email","address","cultureLevel","politicalOutlook","college","officePhone","emergencyContact","emergencyPhone","emergencyAddress","introduction","orderNo","status","remark","accountType","orgId","sysOrg","managerUserId","posId","jobNum","posLevel","posTitle","expertise","officeZone","office","joinDate","lastLoginIp","lastLoginAddress","lastLoginTime","lastLoginDevice","signature"]}}},{"name":"SysUserExtOrg","displayName":"","id":"#/definitions/84275402","description":"系统用户扩展机构表","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"orgId":{"type":"integer","description":"机构Id","format":"int64"},"posId":{"type":"integer","description":"职位Id","format":"int64"},"jobNum":{"maxLength":32,"type":["string","null"],"description":"工号"},"posLevel":{"maxLength":32,"type":["string","null"],"description":"职级"},"joinDate":{"type":["string","null"],"description":"入职日期","format":"date-time"}},"additionalProperties":false,"description":"系统用户扩展机构表","x-apifox-orders":["id","userId","orgId","posId","jobNum","posLevel","joinDate"]}}},{"name":"SysWechatPay","displayName":"","id":"#/definitions/84275403","description":"系统微信支付表","schema":{"jsonSchema":{"required":["appId","merchantId","outTradeNumber","transactionId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"merchantId":{"minLength":1,"type":"string","description":"微信商户号"},"appId":{"minLength":1,"type":"string","description":"服务商AppId"},"outTradeNumber":{"minLength":1,"type":"string","description":"商户订单号"},"transactionId":{"minLength":1,"type":"string","description":"支付订单号"},"tradeType":{"type":["string","null"],"description":"交易类型"},"tradeState":{"type":["string","null"],"description":"交易状态"},"tradeStateDescription":{"type":["string","null"],"description":"交易状态描述"},"bankType":{"type":["string","null"],"description":"付款银行类型"},"total":{"type":"integer","description":"订单总金额","format":"int32"},"payerTotal":{"type":["integer","null"],"description":"用户支付金额","format":"int32"},"successTime":{"type":["string","null"],"description":"支付完成时间","format":"date-time"},"expireTime":{"type":["string","null"],"description":"交易结束时间","format":"date-time"},"description":{"type":["string","null"],"description":"商品描述"},"scene":{"type":["string","null"],"description":"场景信息"},"attachment":{"type":["string","null"],"description":"附加数据"},"goodsTag":{"type":["string","null"],"description":"优惠标记"},"settlement":{"type":["string","null"],"description":"结算信息"},"notifyUrl":{"type":["string","null"],"description":"回调通知地址"},"remark":{"type":["string","null"],"description":"备注"},"openId":{"type":["string","null"],"description":"微信OpenId标识"},"subMerchantId":{"type":["string","null"],"description":"子商户号"},"subAppId":{"type":["string","null"],"description":"子商户AppId"},"subOpenId":{"type":["string","null"],"description":"子商户唯一标识"}},"additionalProperties":false,"description":"系统微信支付表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","merchantId","appId","outTradeNumber","transactionId","tradeType","tradeState","tradeStateDescription","bankType","total","payerTotal","successTime","expireTime","description","scene","attachment","goodsTag","settlement","notifyUrl","remark","openId","subMerchantId","subAppId","subOpenId"]}}},{"name":"SysWechatUser","displayName":"","id":"#/definitions/84275404","description":"系统微信用户表","schema":{"jsonSchema":{"required":["openId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"userId":{"type":"integer","description":"系统用户Id","format":"int64"},"platformType":{"$ref":"#/definitions/84275344"},"openId":{"maxLength":64,"minLength":1,"type":"string","description":"OpenId"},"sessionKey":{"maxLength":256,"type":["string","null"],"description":"会话密钥"},"unionId":{"maxLength":64,"type":["string","null"],"description":"UnionId"},"nickName":{"maxLength":64,"type":["string","null"],"description":"昵称"},"avatar":{"maxLength":256,"type":["string","null"],"description":"头像"},"mobile":{"maxLength":16,"type":["string","null"],"description":"手机号码"},"sex":{"type":["integer","null"],"description":"性别","format":"int32"},"language":{"maxLength":64,"type":["string","null"],"description":"语言"},"city":{"maxLength":64,"type":["string","null"],"description":"城市"},"province":{"maxLength":64,"type":["string","null"],"description":"省"},"country":{"maxLength":64,"type":["string","null"],"description":"国家"},"accessToken":{"type":["string","null"],"description":"AccessToken"},"refreshToken":{"type":["string","null"],"description":"RefreshToken"},"expiresIn":{"type":["integer","null"],"description":"过期时间","format":"int32"},"scope":{"maxLength":64,"type":["string","null"],"description":"用户授权的作用域,使用逗号分隔"}},"additionalProperties":false,"description":"系统微信用户表","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","userId","platformType","openId","sessionKey","unionId","nickName","avatar","mobile","sex","language","city","province","country","accessToken","refreshToken","expiresIn","scope"]}}},{"name":"TableOutput","displayName":"","id":"#/definitions/84275405","description":"数据库表","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"],"description":"库定位器名"},"tableName":{"type":["string","null"],"description":"表名(字母形式的)"},"entityName":{"type":["string","null"],"description":"实体名称"},"createTime":{"type":["string","null"],"description":"创建时间"},"updateTime":{"type":["string","null"],"description":"更新时间"},"tableComment":{"type":["string","null"],"description":"表名称描述(功能名)"}},"additionalProperties":false,"description":"数据库表","x-apifox-orders":["configId","tableName","entityName","createTime","updateTime","tableComment"]}}},{"name":"TenantIdInput","displayName":"","id":"#/definitions/84275406","description":"","schema":{"jsonSchema":{"type":"object","properties":{"tenantId":{"type":"integer","description":"租户Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["tenantId"]}}},{"name":"TenantInput","displayName":"","id":"#/definitions/84275407","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","status"]}}},{"name":"TenantOutput","displayName":"","id":"#/definitions/84275408","description":"","schema":{"jsonSchema":{"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"orgId":{"type":"integer","description":"机构Id","format":"int64"},"host":{"maxLength":128,"type":["string","null"],"description":"主机"},"tenantType":{"$ref":"#/definitions/84275409"},"dbType":{"$ref":"#/definitions/84275276"},"connection":{"maxLength":256,"type":["string","null"],"description":"数据库连接"},"configId":{"maxLength":64,"type":["string","null"],"description":"数据库标识"},"slaveConnections":{"type":["string","null"],"description":"从库连接/读写分离"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"name":{"type":["string","null"],"description":"租户名称"},"adminAccount":{"type":["string","null"],"description":"管理员账号"},"email":{"type":["string","null"],"description":"电子邮箱"},"phone":{"type":["string","null"],"description":"电话"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","userId","orgId","host","tenantType","dbType","connection","configId","slaveConnections","orderNo","remark","status","name","adminAccount","email","phone"]}}},{"name":"TenantTypeEnum","displayName":"","id":"#/definitions/84275409","description":"租户类型枚举
 Id隔离 Id = 0
 库隔离 Db = 1
","schema":{"jsonSchema":{"enum":[0,1],"type":"integer","description":"租户类型枚举
 Id隔离 Id = 0
 库隔离 Db = 1
","format":"int32"}}},{"name":"TenantUserInput","displayName":"","id":"#/definitions/84275410","description":"","schema":{"jsonSchema":{"type":"object","properties":{"userId":{"type":"integer","description":"用户Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["userId"]}}},{"name":"TriggerStatus","displayName":"","id":"#/definitions/84275411","description":"
  Backlog = 0
  Ready = 1
  Running = 2
  Pause = 3
  Blocked = 4
  ErrorToReady = 5
  Archived = 6
  Panic = 7
  Overrun = 8
  Unoccupied = 9
  NotStart = 10
  Unknown = 11
  Unhandled = 12
","schema":{"jsonSchema":{"enum":[0,1,2,3,4,5,6,7,8,9,10,11,12],"type":"integer","description":"
  Backlog = 0
  Ready = 1
  Running = 2
  Pause = 3
  Blocked = 4
  ErrorToReady = 5
  Archived = 6
  Panic = 7
  Overrun = 8
  Unoccupied = 9
  NotStart = 10
  Unknown = 11
  Unhandled = 12
","format":"int32"}}},{"name":"UnlockLoginInput","displayName":"","id":"#/definitions/84275412","description":"解除登录锁定输入参数","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"}},"additionalProperties":false,"description":"解除登录锁定输入参数","x-apifox-orders":["id"]}}},{"name":"UpdateCodeGenInput","displayName":"","id":"#/definitions/84275413","description":"","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"authorName":{"type":["string","null"],"description":"作者姓名"},"className":{"type":["string","null"],"description":"类名"},"tablePrefix":{"type":["string","null"],"description":"是否移除表前缀"},"configId":{"type":["string","null"],"description":"库定位器名"},"dbName":{"type":["string","null"],"description":"数据库名(保留字段)"},"dbType":{"type":["string","null"],"description":"数据库类型"},"connectionString":{"type":["string","null"],"description":"数据库链接"},"generateType":{"type":["string","null"],"description":"生成方式"},"tableName":{"type":["string","null"],"description":"数据库表名"},"nameSpace":{"type":["string","null"],"description":"命名空间"},"busName":{"type":["string","null"],"description":"业务名(业务代码包名称)"},"tableComment":{"type":["string","null"],"description":"功能名(数据库表名称)"},"menuApplication":{"type":["string","null"],"description":"菜单应用分类(应用编码)"},"menuPid":{"type":"integer","description":"菜单父级","format":"int64"},"printType":{"type":["string","null"],"description":"支持打印类型"},"printName":{"type":["string","null"],"description":"打印模版名称"},"id":{"type":"integer","description":"代码生成器Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","authorName","className","tablePrefix","configId","dbName","dbType","connectionString","generateType","tableName","nameSpace","busName","tableComment","menuApplication","menuPid","printType","printName","id"]}}},{"name":"UpdateConfigInput","displayName":"","id":"#/definitions/84275414","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"value":{"maxLength":64,"type":["string","null"],"description":"属性值"},"sysFlag":{"$ref":"#/definitions/84275444"},"groupCode":{"maxLength":64,"type":["string","null"],"description":"分组编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","value","sysFlag","groupCode","orderNo","remark"]}}},{"name":"UpdateDbColumnInput","displayName":"","id":"#/definitions/84275415","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]},"columnName":{"type":["string","null"]},"oldColumnName":{"type":["string","null"]},"description":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","columnName","oldColumnName","description"]}}},{"name":"UpdateDbTableInput","displayName":"","id":"#/definitions/84275416","description":"","schema":{"jsonSchema":{"type":"object","properties":{"configId":{"type":["string","null"]},"tableName":{"type":["string","null"]},"oldTableName":{"type":["string","null"]},"description":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["configId","tableName","oldTableName","description"]}}},{"name":"UpdateDictDataInput","displayName":"","id":"#/definitions/84275417","description":"","schema":{"jsonSchema":{"required":["code","value"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"dictTypeId":{"type":"integer","description":"字典类型Id","format":"int64"},"value":{"maxLength":128,"minLength":1,"type":"string","description":"值"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"tagType":{"maxLength":16,"type":["string","null"],"description":"显示样式-标签颜色"},"styleSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Style(控制显示样式)"},"classSetting":{"maxLength":512,"type":["string","null"],"description":"显示样式-Class(控制显示样式)"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":2048,"type":["string","null"],"description":"备注"},"extData":{"type":["string","null"],"description":"拓展数据(保存业务功能的配置项)"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","dictTypeId","value","code","tagType","styleSetting","classSetting","orderNo","remark","extData","status"]}}},{"name":"UpdateDictTypeInput","displayName":"","id":"#/definitions/84275418","description":"","schema":{"jsonSchema":{"required":["code","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"name":{"maxLength":64,"minLength":1,"type":"string","description":"名称"},"code":{"maxLength":64,"minLength":1,"type":"string","description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275379"},"description":"字典值集合"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","name","code","orderNo","remark","status","children"]}}},{"name":"UpdateJobDetailInput","displayName":"","id":"#/definitions/84275419","description":"","schema":{"jsonSchema":{"required":["jobId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"groupName":{"maxLength":128,"type":["string","null"],"description":"组名称"},"jobType":{"maxLength":128,"type":["string","null"],"description":"作业类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"concurrent":{"type":"boolean","description":"是否并行执行"},"includeAnnotations":{"type":"boolean","description":"是否扫描特性触发器"},"properties":{"type":["string","null"],"description":"额外数据"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createType":{"$ref":"#/definitions/84275308"},"scriptCode":{"type":["string","null"],"description":"脚本代码"},"jobId":{"minLength":2,"type":"string","description":"作业Id"}},"additionalProperties":false,"x-apifox-orders":["id","groupName","jobType","assemblyName","description","concurrent","includeAnnotations","properties","updatedTime","createType","scriptCode","jobId"]}}},{"name":"UpdateJobTriggerInput","displayName":"","id":"#/definitions/84275420","description":"","schema":{"jsonSchema":{"required":["jobId","triggerId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"triggerType":{"maxLength":128,"type":["string","null"],"description":"触发器类型FullName"},"assemblyName":{"maxLength":128,"type":["string","null"],"description":"程序集Name"},"args":{"maxLength":128,"type":["string","null"],"description":"参数"},"description":{"maxLength":128,"type":["string","null"],"description":"描述信息"},"status":{"$ref":"#/definitions/84275411"},"startTime":{"type":["string","null"],"description":"起始时间","format":"date-time"},"endTime":{"type":["string","null"],"description":"结束时间","format":"date-time"},"lastRunTime":{"type":["string","null"],"description":"最近运行时间","format":"date-time"},"nextRunTime":{"type":["string","null"],"description":"下一次运行时间","format":"date-time"},"numberOfRuns":{"type":"integer","description":"触发次数","format":"int64"},"maxNumberOfRuns":{"type":"integer","description":"最大触发次数(0:不限制,n:N次)","format":"int64"},"numberOfErrors":{"type":"integer","description":"出错次数","format":"int64"},"maxNumberOfErrors":{"type":"integer","description":"最大出错次数(0:不限制,n:N次)","format":"int64"},"numRetries":{"type":"integer","description":"重试次数","format":"int32"},"retryTimeout":{"type":"integer","description":"重试间隔时间(ms)","format":"int32"},"startNow":{"type":"boolean","description":"是否立即启动"},"runOnStart":{"type":"boolean","description":"是否启动时执行一次"},"resetOnlyOnce":{"type":"boolean","description":"是否在启动时重置最大触发次数等于一次的作业"},"updatedTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"jobId":{"minLength":2,"type":"string","description":"作业Id"},"triggerId":{"minLength":2,"type":"string","description":"触发器Id"}},"additionalProperties":false,"x-apifox-orders":["id","triggerType","assemblyName","args","description","status","startTime","endTime","lastRunTime","nextRunTime","numberOfRuns","maxNumberOfRuns","numberOfErrors","maxNumberOfErrors","numRetries","retryTimeout","startNow","runOnStart","resetOnlyOnce","updatedTime","jobId","triggerId"]}}},{"name":"UpdateMenuInput","displayName":"","id":"#/definitions/84275421","description":"","schema":{"jsonSchema":{"required":["title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"pid":{"type":"integer","description":"父Id","format":"int64"},"type":{"$ref":"#/definitions/84275319"},"name":{"maxLength":64,"type":["string","null"],"description":"路由名称"},"path":{"maxLength":128,"type":["string","null"],"description":"路由地址"},"component":{"maxLength":128,"type":["string","null"],"description":"组件路径"},"redirect":{"maxLength":128,"type":["string","null"],"description":"重定向"},"permission":{"maxLength":128,"type":["string","null"],"description":"权限标识"},"icon":{"maxLength":128,"type":["string","null"],"description":"图标"},"isIframe":{"type":"boolean","description":"是否内嵌"},"outLink":{"maxLength":256,"type":["string","null"],"description":"外链链接"},"isHide":{"type":"boolean","description":"是否隐藏"},"isKeepAlive":{"type":"boolean","description":"是否缓存"},"isAffix":{"type":"boolean","description":"是否固定"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275390"},"description":"菜单子项"},"title":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","pid","type","name","path","component","redirect","permission","icon","isIframe","outLink","isHide","isKeepAlive","isAffix","orderNo","status","remark","children","title"]}}},{"name":"UpdateNoticeInput","displayName":"","id":"#/definitions/84275422","description":"","schema":{"jsonSchema":{"required":["content","title"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"title":{"maxLength":32,"minLength":1,"type":"string","description":"标题"},"content":{"minLength":1,"type":"string","description":"内容"},"type":{"$ref":"#/definitions/84275325"},"publicUserId":{"type":"integer","description":"发布人Id","format":"int64"},"publicUserName":{"maxLength":32,"type":["string","null"],"description":"发布人姓名"},"publicOrgId":{"type":"integer","description":"发布机构Id","format":"int64"},"publicOrgName":{"maxLength":64,"type":["string","null"],"description":"发布机构名称"},"publicTime":{"type":["string","null"],"description":"发布时间","format":"date-time"},"cancelTime":{"type":["string","null"],"description":"撤回时间","format":"date-time"},"status":{"$ref":"#/definitions/84275324"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","title","content","type","publicUserId","publicUserName","publicOrgId","publicOrgName","publicTime","cancelTime","status"]}}},{"name":"UpdateOpenAccessInput","displayName":"","id":"#/definitions/84275423","description":"","schema":{"jsonSchema":{"required":["accessKey","accessSecret","bindUserId"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"bindTenantId":{"type":"integer","description":"绑定租户Id","format":"int64"},"accessKey":{"minLength":1,"type":"string","description":"身份标识"},"accessSecret":{"minLength":1,"type":"string","description":"密钥"},"bindUserId":{"type":"integer","description":"绑定用户Id","format":"int64"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","bindTenantId","accessKey","accessSecret","bindUserId"]}}},{"name":"UpdateOrgInput","displayName":"","id":"#/definitions/84275424","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"level":{"type":["integer","null"],"description":"级别","format":"int32"},"type":{"maxLength":64,"type":["string","null"],"description":"机构类型-数据字典"},"directorId":{"type":["integer","null"],"description":"负责人Id","format":"int64"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275395"},"description":"机构子项"},"disabled":{"type":"boolean","description":"是否禁止选中"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","pid","code","level","type","directorId","orderNo","status","remark","children","disabled","name"]}}},{"name":"UpdatePluginInput","displayName":"","id":"#/definitions/84275425","description":"","schema":{"jsonSchema":{"required":["csharpCode","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"csharpCode":{"minLength":1,"type":"string","description":"C#代码"},"assemblyName":{"maxLength":512,"type":["string","null"],"description":"程序集名称"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","csharpCode","assemblyName","orderNo","status","remark","name"]}}},{"name":"UpdatePosInput","displayName":"","id":"#/definitions/84275426","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","code","orderNo","remark","status","name"]}}},{"name":"UpdatePrintInput","displayName":"","id":"#/definitions/84275427","description":"","schema":{"jsonSchema":{"required":["name","template"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"template":{"minLength":1,"type":"string","description":"打印模板"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","template","orderNo","status","remark","name"]}}},{"name":"UpdateRegionInput","displayName":"","id":"#/definitions/84275428","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"pid":{"type":"integer","description":"父Id","format":"int64"},"shortName":{"maxLength":32,"type":["string","null"],"description":"简称"},"mergerName":{"maxLength":64,"type":["string","null"],"description":"组合名"},"code":{"maxLength":32,"type":["string","null"],"description":"行政代码"},"zipCode":{"maxLength":6,"type":["string","null"],"description":"邮政编码"},"cityCode":{"maxLength":6,"type":["string","null"],"description":"区号"},"level":{"type":"integer","description":"层级","format":"int32"},"pinYin":{"maxLength":128,"type":["string","null"],"description":"拼音"},"lng":{"type":"number","description":"经度","format":"float"},"lat":{"type":"number","description":"维度","format":"float"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"children":{"type":["array","null"],"items":{"$ref":"#/definitions/84275399"},"description":"机构子项"},"name":{"minLength":1,"type":"string","description":"名称"}},"additionalProperties":false,"x-apifox-orders":["id","pid","shortName","mergerName","code","zipCode","cityCode","level","pinYin","lng","lat","orderNo","remark","children","name"]}}},{"name":"UpdateRoleInput","displayName":"","id":"#/definitions/84275429","description":"","schema":{"jsonSchema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"code":{"maxLength":64,"type":["string","null"],"description":"编码"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"dataScope":{"$ref":"#/definitions/84275269"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"name":{"minLength":1,"type":"string","description":"名称"},"menuIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"菜单Id集合"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","code","orderNo","dataScope","remark","status","name","menuIdList"]}}},{"name":"UpdateTenantInput","displayName":"","id":"#/definitions/84275430","description":"","schema":{"jsonSchema":{"required":["adminAccount","name"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"userId":{"type":"integer","description":"用户Id","format":"int64"},"orgId":{"type":"integer","description":"机构Id","format":"int64"},"host":{"maxLength":128,"type":["string","null"],"description":"主机"},"tenantType":{"$ref":"#/definitions/84275409"},"dbType":{"$ref":"#/definitions/84275276"},"connection":{"maxLength":256,"type":["string","null"],"description":"数据库连接"},"configId":{"maxLength":64,"type":["string","null"],"description":"数据库标识"},"slaveConnections":{"type":["string","null"],"description":"从库连接/读写分离"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"remark":{"maxLength":128,"type":["string","null"],"description":"备注"},"status":{"$ref":"#/definitions/84275375"},"email":{"type":["string","null"],"description":"电子邮箱"},"phone":{"type":["string","null"],"description":"电话"},"name":{"minLength":2,"type":"string","description":"租户名称"},"adminAccount":{"minLength":3,"type":"string","description":"租管账号"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","userId","orgId","host","tenantType","dbType","connection","configId","slaveConnections","orderNo","remark","status","email","phone","name","adminAccount"]}}},{"name":"UpdateUserInput","displayName":"","id":"#/definitions/84275431","description":"更新用户输入参数","schema":{"jsonSchema":{"required":["account","realName"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"nickName":{"maxLength":32,"type":["string","null"],"description":"昵称"},"avatar":{"maxLength":512,"type":["string","null"],"description":"头像"},"sex":{"$ref":"#/definitions/84275305"},"age":{"type":"integer","description":"年龄","format":"int32"},"birthday":{"type":["string","null"],"description":"出生日期","format":"date-time"},"nation":{"maxLength":32,"type":["string","null"],"description":"民族"},"phone":{"maxLength":16,"type":["string","null"],"description":"手机号码"},"cardType":{"$ref":"#/definitions/84275258"},"idCardNum":{"maxLength":32,"type":["string","null"],"description":"身份证号"},"email":{"maxLength":64,"type":["string","null"],"description":"邮箱"},"address":{"maxLength":256,"type":["string","null"],"description":"地址"},"cultureLevel":{"$ref":"#/definitions/84275267"},"politicalOutlook":{"maxLength":16,"type":["string","null"],"description":"政治面貌"},"college":{"maxLength":128,"type":["string","null"],"description":"毕业院校"},"officePhone":{"maxLength":16,"type":["string","null"],"description":"办公电话"},"emergencyContact":{"maxLength":32,"type":["string","null"],"description":"紧急联系人"},"emergencyPhone":{"maxLength":16,"type":["string","null"],"description":"紧急联系人电话"},"emergencyAddress":{"maxLength":256,"type":["string","null"],"description":"紧急联系人地址"},"introduction":{"maxLength":512,"type":["string","null"],"description":"个人简介"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"accountType":{"$ref":"#/definitions/84275167"},"orgId":{"type":"integer","description":"直属机构Id","format":"int64"},"sysOrg":{"$ref":"#/definitions/84275395"},"managerUserId":{"type":["integer","null"],"description":"直属主管Id","format":"int64"},"posId":{"type":"integer","description":"职位Id","format":"int64"},"jobNum":{"maxLength":32,"type":["string","null"],"description":"工号"},"posLevel":{"maxLength":32,"type":["string","null"],"description":"职级"},"posTitle":{"maxLength":32,"type":["string","null"],"description":"职称"},"expertise":{"maxLength":32,"type":["string","null"],"description":"擅长领域"},"officeZone":{"maxLength":32,"type":["string","null"],"description":"办公区域"},"office":{"maxLength":32,"type":["string","null"],"description":"办公室"},"joinDate":{"type":["string","null"],"description":"入职日期","format":"date-time"},"lastLoginIp":{"maxLength":256,"type":["string","null"],"description":"最新登录Ip"},"lastLoginAddress":{"maxLength":128,"type":["string","null"],"description":"最新登录地点"},"lastLoginTime":{"type":["string","null"],"description":"最新登录时间","format":"date-time"},"lastLoginDevice":{"maxLength":128,"type":["string","null"],"description":"最新登录设备"},"signature":{"maxLength":512,"type":["string","null"],"description":"电子签名"},"account":{"minLength":1,"type":"string","description":"账号"},"realName":{"minLength":1,"type":"string","description":"真实姓名"},"roleIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"角色集合"},"extOrgIdList":{"type":["array","null"],"items":{"$ref":"#/definitions/84275402"},"description":"扩展机构集合"}},"additionalProperties":false,"description":"更新用户输入参数","x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","nickName","avatar","sex","age","birthday","nation","phone","cardType","idCardNum","email","address","cultureLevel","politicalOutlook","college","officePhone","emergencyContact","emergencyPhone","emergencyAddress","introduction","orderNo","status","remark","accountType","orgId","sysOrg","managerUserId","posId","jobNum","posLevel","posTitle","expertise","officeZone","office","joinDate","lastLoginIp","lastLoginAddress","lastLoginTime","lastLoginDevice","signature","account","realName","roleIdList","extOrgIdList"]}}},{"name":"UploadFileFromBase64Input","displayName":"","id":"#/definitions/84275432","description":"","schema":{"jsonSchema":{"type":"object","properties":{"fileDataBase64":{"type":["string","null"],"description":"文件内容"},"contentType":{"type":["string","null"],"description":"文件类型( \"image/jpeg\",)"},"fileName":{"type":["string","null"],"description":"文件名称"},"path":{"type":["string","null"],"description":"保存路径"}},"additionalProperties":false,"x-apifox-orders":["fileDataBase64","contentType","fileName","path"]}}},{"name":"UserInput","displayName":"","id":"#/definitions/84275433","description":"设置用户状态输入参数","schema":{"jsonSchema":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"主键Id","format":"int64"},"status":{"$ref":"#/definitions/84275375"}},"additionalProperties":false,"description":"设置用户状态输入参数","x-apifox-orders":["id","status"]}}},{"name":"UserOutput","displayName":"","id":"#/definitions/84275434","description":"","schema":{"jsonSchema":{"required":["account"],"type":"object","properties":{"id":{"type":"integer","description":"雪花Id","format":"int64"},"createTime":{"type":["string","null"],"description":"创建时间","format":"date-time"},"updateTime":{"type":["string","null"],"description":"更新时间","format":"date-time"},"createUserId":{"type":["integer","null"],"description":"创建者Id","format":"int64"},"createUserName":{"type":["string","null"],"description":"创建者姓名"},"updateUserId":{"type":["integer","null"],"description":"修改者Id","format":"int64"},"updateUserName":{"type":["string","null"],"description":"修改者姓名"},"isDelete":{"type":"boolean","description":"软删除"},"tenantId":{"type":["integer","null"],"description":"租户Id","format":"int64"},"account":{"maxLength":32,"minLength":1,"type":"string","description":"账号"},"realName":{"maxLength":32,"type":["string","null"],"description":"真实姓名"},"nickName":{"maxLength":32,"type":["string","null"],"description":"昵称"},"avatar":{"maxLength":512,"type":["string","null"],"description":"头像"},"sex":{"$ref":"#/definitions/84275305"},"age":{"type":"integer","description":"年龄","format":"int32"},"birthday":{"type":["string","null"],"description":"出生日期","format":"date-time"},"nation":{"maxLength":32,"type":["string","null"],"description":"民族"},"phone":{"maxLength":16,"type":["string","null"],"description":"手机号码"},"cardType":{"$ref":"#/definitions/84275258"},"idCardNum":{"maxLength":32,"type":["string","null"],"description":"身份证号"},"email":{"maxLength":64,"type":["string","null"],"description":"邮箱"},"address":{"maxLength":256,"type":["string","null"],"description":"地址"},"cultureLevel":{"$ref":"#/definitions/84275267"},"politicalOutlook":{"maxLength":16,"type":["string","null"],"description":"政治面貌"},"college":{"maxLength":128,"type":["string","null"],"description":"毕业院校"},"officePhone":{"maxLength":16,"type":["string","null"],"description":"办公电话"},"emergencyContact":{"maxLength":32,"type":["string","null"],"description":"紧急联系人"},"emergencyPhone":{"maxLength":16,"type":["string","null"],"description":"紧急联系人电话"},"emergencyAddress":{"maxLength":256,"type":["string","null"],"description":"紧急联系人地址"},"introduction":{"maxLength":512,"type":["string","null"],"description":"个人简介"},"orderNo":{"type":"integer","description":"排序","format":"int32"},"status":{"$ref":"#/definitions/84275375"},"remark":{"maxLength":256,"type":["string","null"],"description":"备注"},"accountType":{"$ref":"#/definitions/84275167"},"orgId":{"type":"integer","description":"直属机构Id","format":"int64"},"sysOrg":{"$ref":"#/definitions/84275395"},"managerUserId":{"type":["integer","null"],"description":"直属主管Id","format":"int64"},"posId":{"type":"integer","description":"职位Id","format":"int64"},"jobNum":{"maxLength":32,"type":["string","null"],"description":"工号"},"posLevel":{"maxLength":32,"type":["string","null"],"description":"职级"},"posTitle":{"maxLength":32,"type":["string","null"],"description":"职称"},"expertise":{"maxLength":32,"type":["string","null"],"description":"擅长领域"},"officeZone":{"maxLength":32,"type":["string","null"],"description":"办公区域"},"office":{"maxLength":32,"type":["string","null"],"description":"办公室"},"joinDate":{"type":["string","null"],"description":"入职日期","format":"date-time"},"lastLoginIp":{"maxLength":256,"type":["string","null"],"description":"最新登录Ip"},"lastLoginAddress":{"maxLength":128,"type":["string","null"],"description":"最新登录地点"},"lastLoginTime":{"type":["string","null"],"description":"最新登录时间","format":"date-time"},"lastLoginDevice":{"maxLength":128,"type":["string","null"],"description":"最新登录设备"},"signature":{"maxLength":512,"type":["string","null"],"description":"电子签名"},"orgName":{"type":["string","null"],"description":"机构名称"},"posName":{"type":["string","null"],"description":"职位名称"},"roleName":{"type":["string","null"],"description":"角色名称"}},"additionalProperties":false,"x-apifox-orders":["id","createTime","updateTime","createUserId","createUserName","updateUserId","updateUserName","isDelete","tenantId","account","realName","nickName","avatar","sex","age","birthday","nation","phone","cardType","idCardNum","email","address","cultureLevel","politicalOutlook","college","officePhone","emergencyContact","emergencyPhone","emergencyAddress","introduction","orderNo","status","remark","accountType","orgId","sysOrg","managerUserId","posId","jobNum","posLevel","posTitle","expertise","officeZone","office","joinDate","lastLoginIp","lastLoginAddress","lastLoginTime","lastLoginDevice","signature","orgName","posName","roleName"]}}},{"name":"UserRoleInput","displayName":"","id":"#/definitions/84275435","description":"授权用户角色","schema":{"jsonSchema":{"type":"object","properties":{"userId":{"type":"integer","description":"用户Id","format":"int64"},"roleIdList":{"type":["array","null"],"items":{"type":"integer","format":"int64"},"description":"角色Id集合"}},"additionalProperties":false,"description":"授权用户角色","x-apifox-orders":["userId","roleIdList"]}}},{"name":"WechatPayOutput","displayName":"","id":"#/definitions/84275436","description":"","schema":{"jsonSchema":{"type":"object","properties":{"openId":{"type":["string","null"],"description":"OpenId"},"total":{"type":"integer","description":"订单金额","format":"int32"},"attachment":{"type":["string","null"],"description":"附加数据"},"goodsTag":{"type":["string","null"],"description":"优惠标记"}},"additionalProperties":false,"x-apifox-orders":["openId","total","attachment","goodsTag"]}}},{"name":"WechatPayParaInput","displayName":"","id":"#/definitions/84275437","description":"","schema":{"jsonSchema":{"type":"object","properties":{"prepayId":{"type":["string","null"],"description":"订单Id"}},"additionalProperties":false,"x-apifox-orders":["prepayId"]}}},{"name":"WechatPayTransactionInput","displayName":"","id":"#/definitions/84275438","description":"","schema":{"jsonSchema":{"type":"object","properties":{"openId":{"type":["string","null"],"description":"OpenId"},"total":{"type":"integer","description":"订单金额","format":"int32"},"description":{"type":["string","null"],"description":"商品描述"},"attachment":{"type":["string","null"],"description":"附加数据"},"goodsTag":{"type":["string","null"],"description":"优惠标记"}},"additionalProperties":false,"x-apifox-orders":["openId","total","description","attachment","goodsTag"]}}},{"name":"WechatUserInput","displayName":"","id":"#/definitions/84275439","description":"","schema":{"jsonSchema":{"type":"object","properties":{"page":{"type":"integer","description":"当前页码","format":"int32"},"pageSize":{"type":"integer","description":"页码容量","format":"int32"},"field":{"type":["string","null"],"description":"排序字段"},"order":{"type":["string","null"],"description":"排序方向"},"descStr":{"type":["string","null"],"description":"降序排序"},"nickName":{"type":["string","null"],"description":"昵称"},"phoneNumber":{"type":["string","null"],"description":"手机号码"}},"additionalProperties":false,"x-apifox-orders":["page","pageSize","field","order","descStr","nickName","phoneNumber"]}}},{"name":"WechatUserLogin","displayName":"","id":"#/definitions/84275440","description":"微信用户登录","schema":{"jsonSchema":{"required":["openId"],"type":"object","properties":{"openId":{"minLength":10,"type":"string","description":"OpenId"}},"additionalProperties":false,"description":"微信用户登录","x-apifox-orders":["openId"]}}},{"name":"WxOpenIdLoginInput","displayName":"","id":"#/definitions/84275441","description":"微信小程序登录","schema":{"jsonSchema":{"required":["openId"],"type":"object","properties":{"openId":{"minLength":10,"type":"string","description":"OpenId"}},"additionalProperties":false,"description":"微信小程序登录","x-apifox-orders":["openId"]}}},{"name":"WxOpenIdOutput","displayName":"","id":"#/definitions/84275442","description":"","schema":{"jsonSchema":{"type":"object","properties":{"openId":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["openId"]}}},{"name":"WxPhoneOutput","displayName":"","id":"#/definitions/84275443","description":"","schema":{"jsonSchema":{"type":"object","properties":{"phoneNumber":{"type":["string","null"]}},"additionalProperties":false,"x-apifox-orders":["phoneNumber"]}}},{"name":"YesNoEnum","displayName":"","id":"#/definitions/84275444","description":"是否枚举
 是 Y = 1
 否 N = 2
","schema":{"jsonSchema":{"enum":[1,2],"type":"integer","description":"是否枚举
 是 Y = 1
 否 N = 2
","format":"int32"}}}]}]}],"responseCollection":[{"id":4088603,"createdAt":"2024-02-29T09:40:10.000Z","updatedAt":"2024-02-29T09:40:10.000Z","deletedAt":null,"name":"根目录","type":"root","description":"","children":[],"auth":{},"projectId":4084018,"projectBranchId":0,"parentId":0,"createdById":1145276,"updatedById":1145276,"items":[]}],"environments":[{"name":"开发环境","parameters":{"cookie":[],"query":[],"header":[],"body":[]},"variables":[],"type":"normal","visibility":"protected","ordering":0,"tags":[{"name":"","color":"#9373EE"}],"id":"18499638","baseUrl":"http://localhost:5005","baseUrls":{"default":"http://localhost:5005"}}],"commonScripts":[],"globalVariables":[],"commonParameters":null,"projectSetting":{"id":"4084141","auth":{"type":"bearer","bearer":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEzMDAwMDAwMDAxMTEsIlRlbmFudElkIjoxMzAwMDAwMDAwMDAxLCJBY2NvdW50IjoiYWRtaW4iLCJSZWFsTmFtZSI6Iuezu-e7n-euoeeQhuWRmCIsIkFjY291bnRUeXBlIjo4ODgsIk9yZ0lkIjoxMzAwMDAwMDAwMTAxLCJPcmdOYW1lIjoi5aSn5ZCN56eR5oqAIiwiT3JnVHlwZSI6IjEwMSIsImlhdCI6MTcwOTgwMjI5NiwibmJmIjoxNzA5ODAyMjk2LCJleHAiOjE3MTA0MDcwOTYsImlzcyI6IkFkbWluLk5FVCIsImF1ZCI6IkFkbWluLk5FVCJ9.w18JSugPFU50eMOkaLDwq2gWxPNO_3znLeUTgu2i7Ec"}},"servers":[{"id":"default","name":"默认服务"}],"gateway":[],"language":"zh-CN","apiStatuses":["developing","testing","released","deprecated"],"mockSettings":{},"preProcessors":[],"postProcessors":[],"advancedSettings":{"responseValidate":false,"enableJsonc":true,"isDefaultUrlEncoding":2,"enableBigint":false,"preferredHttpVersion":{},"enableTestScenarioSetting":false,"enableYAPICompatScript":false},"initialDisabledMockIds":[],"cloudMock":{"security":"free","enable":false,"tokenKey":"apifoxToken"}},"projectAssociations":[]} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs new file mode 100644 index 00000000..929d28ff --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs @@ -0,0 +1,55 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 用户登录参数 +/// +public class LoginInput +{ + /// + /// 账号 + /// + /// admin + [Required(ErrorMessage = "账号不能为空"), MinLength(2, ErrorMessage = "账号不能少于2个字符")] + public string Account { get; set; } + + /// + /// 密码 + /// + /// 123456 + [Required(ErrorMessage = "密码不能为空"), MinLength(3, ErrorMessage = "密码不能少于3个字符")] + public string Password { get; set; } + + /// + /// 验证码Id + /// + public long CodeId { get; set; } + + /// + /// 验证码 + /// + public string Code { get; set; } +} + +public class LoginPhoneInput +{ + /// + /// 手机号码 + /// + /// admin + [Required(ErrorMessage = "手机号码不能为空")] + [DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "手机号码不正确")] + public string Phone { get; set; } + + /// + /// 验证码 + /// + /// 123456 + [Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")] + public string Code { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginOutput.cs b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginOutput.cs new file mode 100644 index 00000000..1ee6ba55 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginOutput.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 用户登录结果 +/// +public class LoginOutput +{ + /// + /// 令牌Token + /// + public string AccessToken { get; set; } + + /// + /// 刷新Token + /// + public string RefreshToken { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginUserOutput.cs b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginUserOutput.cs new file mode 100644 index 00000000..d573a194 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginUserOutput.cs @@ -0,0 +1,98 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 用户登录信息 +/// +public class LoginUserOutput +{ + /// + /// 用户id + /// + public long Id { get; set; } + + /// + /// 账号名称 + /// + public string Account { get; set; } + + /// + /// 真实姓名 + /// + public string RealName { get; set; } + + /// + /// 电话 + /// + public string Phone { get; set; } + + /// + /// 身份证 + /// + public string IdCardNum { get; set; } + + /// + /// 邮箱 + /// + public string Email { get; set; } + + /// + /// 账号类型 + /// + public AccountTypeEnum AccountType { get; set; } = AccountTypeEnum.NormalUser; + + /// + /// 头像 + /// + public string Avatar { get; set; } + + /// + /// 个人简介 + /// + public string Introduction { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 电子签名 + /// + public string Signature { get; set; } + + /// + /// 机构Id + /// + public long OrgId { get; set; } + + /// + /// 机构名称 + /// + public string OrgName { get; set; } + + /// + /// 机构类型 + /// + public string OrgType { get; set; } + + /// + /// 职位名称 + /// + public string PosName { get; set; } + + /// + /// 按钮权限集合 + /// + public List Buttons { get; set; } + + /// + /// 角色集合 + /// + public List RoleIds { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs new file mode 100644 index 00000000..009000b2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统域登录信息配置输入参数 +/// +public class SysLdapInput : BasePageInput +{ + /// + /// 关键字查询 + /// + public string? SearchKey { get; set; } + + /// + /// 主机 + /// + public string? Host { get; set; } +} + +public class AddSysLdapInput : SysLdap +{ +} + +public class UpdateSysLdapInput : SysLdap +{ +} + +public class DeleteSysLdapInput : BaseIdInput +{ +} + +public class DetailSysLdapInput : BaseIdInput +{ +} + +public class SyncSysLdapInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs new file mode 100644 index 00000000..88f3c15f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs @@ -0,0 +1,390 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion.SpecificationDocument; +using Lazy.Captcha.Core; + +namespace Admin.NET.Core.Service; + +/// +/// 系统登录授权服务 🧩 +/// +[ApiDescriptionSettings(Order = 500)] +public class SysAuthService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysUserRep; + private readonly SqlSugarRepository _sysUserLdap; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly SysMenuService _sysMenuService; + private readonly SysOnlineUserService _sysOnlineUserService; + private readonly SysConfigService _sysConfigService; + private readonly ICaptcha _captcha; + private readonly SysCacheService _sysCacheService; + private readonly SysLdapService _sysLdapService; + + public SysAuthService(UserManager userManager, + SqlSugarRepository sysUserRep, + SqlSugarRepository sysUserLdapRep, + IHttpContextAccessor httpContextAccessor, + SysMenuService sysMenuService, + SysOnlineUserService sysOnlineUserService, + SysConfigService sysConfigService, + ICaptcha captcha, + SysCacheService sysCacheService, + SysLdapService sysLdapService) + { + _userManager = userManager; + _sysUserRep = sysUserRep; + _sysUserLdap = sysUserLdapRep; + _httpContextAccessor = httpContextAccessor; + _sysMenuService = sysMenuService; + _sysOnlineUserService = sysOnlineUserService; + _sysConfigService = sysConfigService; + _captcha = captcha; + _sysCacheService = sysCacheService; + _sysLdapService = sysLdapService; + } + + /// + /// 账号密码登录 🔖 + /// + /// + /// 用户名/密码:superadmin/123456 + /// + [AllowAnonymous] + [DisplayName("账号密码登录")] + public virtual async Task Login([Required] LoginInput input) + { + //// 可以根据域名获取具体租户 + //var host = _httpContextAccessor.HttpContext.Request.Host; + + // 判断密码错误次数(默认5次,缓存30分钟) + var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{input.Account}"; + var errorPasswordCount = _sysCacheService.Get(keyErrorPasswordCount); + if (errorPasswordCount >= 5) + throw Oops.Oh(ErrorCodeEnum.D1027); + + // 是否开启验证码 + if (await _sysConfigService.GetConfigValue(CommonConst.SysCaptcha)) + { + // 判断验证码 + if (!_captcha.Validate(input.CodeId.ToString(), input.Code)) + throw Oops.Oh(ErrorCodeEnum.D0008); + } + + // 账号是否存在 + var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Account.Equals(input.Account)); + _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + // 账号是否被冻结 + if (user.Status == StatusEnum.Disable) + throw Oops.Oh(ErrorCodeEnum.D1017); + + // 租户是否被禁用 + var tenant = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.TenantId); + if (tenant != null && tenant.Status == StatusEnum.Disable) + throw Oops.Oh(ErrorCodeEnum.Z1003); + + // 国密SM2解密(前端密码传输SM2加密后的) + try + { + input.Password = CryptogramUtil.SM2Decrypt(input.Password); + } + catch + { + throw Oops.Oh(ErrorCodeEnum.D0010); + } + + // 是否开启域登录验证 + if (await _sysConfigService.GetConfigValue(CommonConst.SysDomainLogin)) + { + var userLdap = await _sysUserLdap.GetFirstAsync(u => u.UserId == user.Id && u.TenantId == tenant.Id); + if (userLdap == null) + { + VerifyPassword(input, keyErrorPasswordCount, errorPasswordCount, user); + } + else if (!await _sysLdapService.AuthAccount(tenant.Id, userLdap.Account, input.Password)) + { + _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); + throw Oops.Oh(ErrorCodeEnum.D1000); + } + } + else + VerifyPassword(input, keyErrorPasswordCount, errorPasswordCount, user); + + // 登录成功则清空密码错误次数 + _sysCacheService.Remove(keyErrorPasswordCount); + + return await CreateToken(user); + } + + /// + /// 验证用户密码 + /// + /// + /// + /// + /// + private void VerifyPassword(LoginInput input, string keyErrorPasswordCount, int errorPasswordCount, SysUser user) + { + if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + { + if (!user.Password.Equals(MD5Encryption.Encrypt(input.Password))) + { + _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); + throw Oops.Oh(ErrorCodeEnum.D1000); + } + } + else + { + if (!CryptogramUtil.Decrypt(user.Password).Equals(input.Password)) + { + _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); + throw Oops.Oh(ErrorCodeEnum.D1000); + } + } + } + + /// + /// 验证锁屏密码 🔖 + /// + /// + /// + [DisplayName("验证锁屏密码")] + public virtual async Task UnLockScreen([Required, FromQuery] string password) + { + // 账号是否存在 + var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId); + _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + // 国密SM2解密(前端密码传输SM2加密后的) + password = CryptogramUtil.SM2Decrypt(password); + + // 密码是否正确 + if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + { + if (!user.Password.Equals(MD5Encryption.Encrypt(password))) + throw Oops.Oh(ErrorCodeEnum.D1000); + } + else + { + if (!CryptogramUtil.Decrypt(user.Password).Equals(password)) + throw Oops.Oh(ErrorCodeEnum.D1000); + } + + return true; + } + + /// + /// 手机号登录 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("手机号登录")] + public virtual async Task LoginPhone([Required] LoginPhoneInput input) + { + var verifyCode = _sysCacheService.Get($"{CacheConst.KeyPhoneVerCode}{input.Phone}"); + if (string.IsNullOrWhiteSpace(verifyCode)) + throw Oops.Oh("验证码不存在或已失效,请重新获取!"); + if (verifyCode != input.Code) + throw Oops.Oh("验证码错误!"); + + // 账号是否存在 + var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone)); + _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + return await CreateToken(user); + } + + /// + /// 生成Token令牌 🔖 + /// + /// + /// + [NonAction] + public virtual async Task CreateToken(SysUser user) + { + // 单用户登录 + await _sysOnlineUserService.SingleLogin(user.Id); + + // 生成Token令牌 + var tokenExpire = await _sysConfigService.GetTokenExpire(); + var accessToken = JWTEncryption.Encrypt(new Dictionary + { + { ClaimConst.UserId, user.Id }, + { ClaimConst.TenantId, user.TenantId }, + { ClaimConst.Account, user.Account }, + { ClaimConst.RealName, user.RealName }, + { ClaimConst.AccountType, user.AccountType }, + { ClaimConst.OrgId, user.OrgId }, + { ClaimConst.OrgName, user.SysOrg?.Name }, + { ClaimConst.OrgType, user.SysOrg?.Type }, + }, tokenExpire); + + // 生成刷新Token令牌 + var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire(); + var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire); + + // 设置响应报文头 + _httpContextAccessor.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken); + + // Swagger Knife4UI-AfterScript登录脚本 + // ke.global.setAllHeader('Authorization', 'Bearer ' + ke.response.headers['access-token']); + + return new LoginOutput + { + AccessToken = accessToken, + RefreshToken = refreshToken + }; + } + + /// + /// 获取登录账号 🔖 + /// + /// + [DisplayName("获取登录账号")] + public virtual async Task GetUserInfo() + { + var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D1011).StatusCode(401); + // 获取机构 + var org = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.OrgId); + // 获取职位 + var pos = await _sysUserRep.ChangeRepository>().GetFirstAsync(u => u.Id == user.PosId); + // 获取按钮集合 + var buttons = await _sysMenuService.GetOwnBtnPermList(); + // 获取角色集合 + var roleIds = await _sysUserRep.ChangeRepository>().AsQueryable() + .Where(u => u.UserId == user.Id).Select(u => u.RoleId).ToListAsync(); + + return new LoginUserOutput + { + Id = user.Id, + Account = user.Account, + RealName = user.RealName, + Phone = user.Phone, + IdCardNum = user.IdCardNum, + Email = user.Email, + AccountType = user.AccountType, + Avatar = user.Avatar, + Address = user.Address, + Signature = user.Signature, + OrgId = user.OrgId, + OrgName = org?.Name, + OrgType = org?.Type, + PosName = pos?.Name, + Buttons = buttons, + RoleIds = roleIds + }; + } + + /// + /// 获取刷新Token 🔖 + /// + /// + /// + [DisplayName("获取刷新Token")] + public virtual string GetRefreshToken([FromQuery] string accessToken) + { + var refreshTokenExpire = _sysConfigService.GetRefreshTokenExpire().GetAwaiter().GetResult(); + return JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire); + } + + /// + /// 退出系统 🔖 + /// + [DisplayName("退出系统")] + public void Logout() + { + if (string.IsNullOrWhiteSpace(_userManager.Account)) + throw Oops.Oh(ErrorCodeEnum.D1011); + + _httpContextAccessor.HttpContext.SignoutToSwagger(); + } + + /// + /// 获取登录配置 🔖 + /// + /// + [AllowAnonymous] + [SuppressMonitor] + [DisplayName("获取登录配置")] + public async Task GetLoginConfig() + { + var secondVerEnabled = await _sysConfigService.GetConfigValue(CommonConst.SysSecondVer); + var captchaEnabled = await _sysConfigService.GetConfigValue(CommonConst.SysCaptcha); + return new { SecondVerEnabled = secondVerEnabled, CaptchaEnabled = captchaEnabled }; + } + + /// + /// 获取水印配置 🔖 + /// + /// + [SuppressMonitor] + [DisplayName("获取水印配置")] + public async Task GetWatermarkConfig() + { + var watermarkEnabled = await _sysConfigService.GetConfigValue(CommonConst.SysWatermark); + return new { WatermarkEnabled = watermarkEnabled }; + } + + /// + /// 获取验证码 🔖 + /// + /// + [AllowAnonymous] + [SuppressMonitor] + [DisplayName("获取验证码")] + public dynamic GetCaptcha() + { + var codeId = YitIdHelper.NextId().ToString(); + var captcha = _captcha.Generate(codeId); + return new { Id = codeId, Img = captcha.Base64 }; + } + + /// + /// Swagger登录检查 🔖 + /// + /// + [AllowAnonymous] + [HttpPost("/api/swagger/checkUrl"), NonUnify] + [DisplayName("Swagger登录检查")] + public int SwaggerCheckUrl() + { + return _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated ? 200 : 401; + } + + /// + /// Swagger登录提交 🔖 + /// + /// + /// + [AllowAnonymous] + [HttpPost("/api/swagger/submitUrl"), NonUnify] + [DisplayName("Swagger登录提交")] + public async Task SwaggerSubmitUrl([FromForm] SpecificationAuth auth) + { + try + { + _sysCacheService.Set($"{CacheConst.KeyConfig}{CommonConst.SysCaptcha}", false); + + await Login(new LoginInput + { + Account = auth.UserName, + Password = CryptogramUtil.SM2Encrypt(auth.Password), + }); + + _sysCacheService.Remove($"{CacheConst.KeyConfig}{CommonConst.SysCaptcha}"); + + return 200; + } + catch (Exception) + { + return 401; + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs new file mode 100644 index 00000000..2514891f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs @@ -0,0 +1,402 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Novell.Directory.Ldap; + +namespace Admin.NET.Core; + +/// +/// 系统域登录配置服务 🧩 +/// +[ApiDescriptionSettings(Order = 496)] +public class SysLdapService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysLdapRep; + + public SysLdapService(SqlSugarRepository sysLdapRep) + { + _sysLdapRep = sysLdapRep; + } + + /// + /// 获取系统域登录配置分页列表 🔖 + /// + /// + /// + [DisplayName("获取系统域登录配置分页列表")] + public async Task> Page(SysLdapInput input) + { + return await _sysLdapRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), u => u.Host.Contains(input.SearchKey.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.Host), u => u.Host.Contains(input.Host.Trim())) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加系统域登录配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加系统域登录配置")] + public async Task Add(AddSysLdapInput input) + { + var entity = input.Adapt(); + entity.BindPass = CryptogramUtil.Encrypt(input.BindPass); + await _sysLdapRep.InsertAsync(entity); + return entity.Id; + } + + /// + /// 更新系统域登录配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新系统域登录配置")] + public async Task Update(UpdateSysLdapInput input) + { + var entity = input.Adapt(); + if (!string.IsNullOrEmpty(input.BindPass) && input.BindPass.Length < 32) + { + entity.BindPass = CryptogramUtil.Encrypt(input.BindPass); // 加密 + } + + await _sysLdapRep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); + } + + /// + /// 删除系统域登录配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除系统域登录配置")] + public async Task Delete(DeleteSysLdapInput input) + { + var entity = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + await _sysLdapRep.FakeDeleteAsync(entity); // 假删除 + //await _rep.DeleteAsync(entity); // 真删除 + } + + /// + /// 获取系统域登录配置详情 🔖 + /// + /// + /// + [DisplayName("获取系统域登录配置详情")] + public async Task GetDetail([FromQuery] DetailSysLdapInput input) + { + return await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id); + } + + /// + /// 获取系统域登录配置列表 🔖 + /// + /// + [DisplayName("获取系统域登录配置列表")] + public async Task> GetList() + { + return await _sysLdapRep.AsQueryable().Select().ToListAsync(); + } + + /// + /// 验证账号 + /// + /// 域用户 + /// 密码 + /// 租户 + /// + [NonAction] + public async Task AuthAccount(long tenantId, string account, string password) + { + var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.TenantId == tenantId) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + var ldapConn = new LdapConnection(); + try + { + ldapConn.Connect(sysLdap.Host, sysLdap.Port); + ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, sysLdap.BindPass); + var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeSub, sysLdap.AuthFilter.Replace("$s", account), null, false); + string dn = string.Empty; + while (ldapSearchResults.HasMore()) + { + var ldapEntry = ldapSearchResults.Next(); + var sAMAccountName = ldapEntry.GetAttribute(sysLdap.AuthFilter)?.StringValue; + if (!string.IsNullOrEmpty(sAMAccountName)) + { + dn = ldapEntry.Dn; + break; + } + } + + if (string.IsNullOrEmpty(dn)) throw Oops.Oh(ErrorCodeEnum.D1002); + // var attr = new LdapAttribute("userPassword", password); + ldapConn.Bind(dn, password); + } + catch (LdapException e) + { + return e.ResultCode switch + { + LdapException.NoSuchObject or LdapException.NoSuchAttribute => throw Oops.Oh(ErrorCodeEnum.D0009), + LdapException.InvalidCredentials => false, + _ => throw Oops.Oh(e.Message), + }; + } + finally + { + ldapConn.Disconnect(); + } + + return true; + } + + /// + /// 同步域用户 🔖 + /// + /// + /// + [DisplayName("同步域用户")] + public async Task SyncUser(SyncSysLdapInput input) + { + var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + var ldapConn = new LdapConnection(); + try + { + ldapConn.Connect(sysLdap.Host, sysLdap.Port); + ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, sysLdap.BindPass); + var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); + var userLdapList = new List(); + while (ldapSearchResults.HasMore()) + { + LdapEntry ldapEntry; + try + { + ldapEntry = ldapSearchResults.Next(); + if (ldapEntry == null) continue; + } + catch (LdapException) + { + continue; + } + + var attrs = ldapEntry.GetAttributeSet(); + var deptCode = GetDepartmentCode(attrs, sysLdap.BindAttrCode); + if (attrs.Count == 0 || attrs.ContainsKey("OU")) + { + SearchDnLdapUser(ldapConn, sysLdap, userLdapList, ldapEntry.Dn, deptCode); + } + else + { + var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode); + if (string.IsNullOrEmpty(sysUserLdap.EmployeeId)) continue; + userLdapList.Add(sysUserLdap); + } + } + + if (userLdapList.Count == 0) + return; + + await App.GetRequiredService().InsertUserLdaps(sysLdap.TenantId!.Value, userLdapList); + } + catch (LdapException e) + { + throw e.ResultCode switch + { + LdapException.NoSuchObject or LdapException.NoSuchAttribute => Oops.Oh(ErrorCodeEnum.D0009), + _ => Oops.Oh(e.Message), + }; + } + finally + { + ldapConn.Disconnect(); + } + } + + /// + /// 获取部门代码 + /// + /// + /// + /// + private static string GetDepartmentCode(LdapAttributeSet attrs, string bindAttrCode) + { + return bindAttrCode == "objectGUID" + ? new Guid(attrs.GetAttribute(bindAttrCode)?.ByteValue).ToString() + : attrs.GetAttribute(bindAttrCode)?.StringValue ?? "0"; + } + + /// + /// 创建同步对象 + /// + /// + /// + /// + /// + /// + private static SysUserLdap CreateSysUserLdap(LdapAttributeSet attrs, string bindAttrAccount, string bindAttrEmployeeId, string deptCode) + { + return new SysUserLdap + { + Account = !attrs.ContainsKey(bindAttrAccount) ? null : attrs.GetAttribute(bindAttrAccount)?.StringValue, + EmployeeId = !attrs.ContainsKey(bindAttrEmployeeId) ? null : attrs.GetAttribute(bindAttrEmployeeId)?.StringValue, + DeptCode = deptCode + }; + } + + /// + /// 遍历查询域用户 + /// + /// + /// + /// + /// + /// + private static void SearchDnLdapUser(LdapConnection ldapConn, SysLdap sysLdap, List userLdapList, string baseDn, string deptCode) + { + var ldapSearchResults = ldapConn.Search(baseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); + while (ldapSearchResults.HasMore()) + { + LdapEntry ldapEntry; + try + { + ldapEntry = ldapSearchResults.Next(); + if (ldapEntry == null) continue; + } + catch (LdapException) + { + continue; + } + + var attrs = ldapEntry.GetAttributeSet(); + deptCode = GetDepartmentCode(attrs, sysLdap.BindAttrCode); + + if (attrs.Count == 0 || attrs.ContainsKey("OU")) + SearchDnLdapUser(ldapConn, sysLdap, userLdapList, ldapEntry.Dn, deptCode); + else + { + var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode); + + if (string.IsNullOrEmpty(sysUserLdap.EmployeeId)) continue; + userLdapList.Add(sysUserLdap); + } + } + } + + /// + /// 同步域组织 🔖 + /// + /// + /// + [DisplayName("同步域组织")] + public async Task SyncDept(SyncSysLdapInput input) + { + var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + var ldapConn = new LdapConnection(); + try + { + ldapConn.Connect(sysLdap.Host, sysLdap.Port); + ldapConn.Bind(sysLdap.Version, sysLdap.BindDn, sysLdap.BindPass); + var ldapSearchResults = ldapConn.Search(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); + var orgList = new List(); + while (ldapSearchResults.HasMore()) + { + LdapEntry ldapEntry; + try + { + ldapEntry = ldapSearchResults.Next(); + if (ldapEntry == null) continue; + } + catch (LdapException) + { + continue; + } + + var attrs = ldapEntry.GetAttributeSet(); + if (attrs.Count == 0 || attrs.ContainsKey("OU")) + { + var sysOrg = CreateSysOrg(attrs, sysLdap, orgList, new SysOrg { Id = 0, Level = 0 }); + orgList.Add(sysOrg); + + SearchDnLdapDept(ldapConn, sysLdap, orgList, ldapEntry.Dn, sysOrg); + } + } + + if (orgList.Count == 0) + return; + + await App.GetRequiredService().BatchAddOrgs(orgList); + } + catch (LdapException e) + { + throw e.ResultCode switch + { + LdapException.NoSuchObject or LdapException.NoSuchAttribute => Oops.Oh(ErrorCodeEnum.D0009), + _ => Oops.Oh(e.Message), + }; + } + finally + { + ldapConn.Disconnect(); + } + } + + /// + /// 遍历查询域用户 + /// + /// + /// + /// + /// + /// + private static void SearchDnLdapDept(LdapConnection ldapConn, SysLdap sysLdap, List listOrgs, string baseDn, SysOrg org) + { + var ldapSearchResults = ldapConn.Search(baseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); + while (ldapSearchResults.HasMore()) + { + LdapEntry ldapEntry; + try + { + ldapEntry = ldapSearchResults.Next(); + if (ldapEntry == null) continue; + } + catch (LdapException) + { + continue; + } + + var attrs = ldapEntry.GetAttributeSet(); + if (attrs.Count == 0 || attrs.ContainsKey("OU")) + { + var sysOrg = CreateSysOrg(attrs, sysLdap, listOrgs, org); + listOrgs.Add(sysOrg); + + SearchDnLdapDept(ldapConn, sysLdap, listOrgs, ldapEntry.Dn, sysOrg); + } + } + } + + /// + /// 创建架构对象 + /// + /// + /// + /// + /// + /// + private static SysOrg CreateSysOrg(LdapAttributeSet attrs, SysLdap sysLdap, List listOrgs, SysOrg org) + { + return new SysOrg + { + Pid = org.Id, + Id = YitIdHelper.NextId(), + Code = !attrs.ContainsKey(sysLdap.BindAttrCode) ? null : new Guid(attrs.GetAttribute(sysLdap.BindAttrCode)?.ByteValue).ToString(), + Level = org.Level + 1, + Name = !attrs.ContainsKey(sysLdap.BindAttrAccount) ? null : attrs.GetAttribute(sysLdap.BindAttrAccount)?.StringValue, + OrderNo = listOrgs.Count + 1, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/BaseService.cs b/Admin.NET/Admin.NET.Core/Service/BaseService.cs new file mode 100644 index 00000000..51a46a6f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/BaseService.cs @@ -0,0 +1,90 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 实体操作基服务 +/// +/// +public class BaseService : IDynamicApiController where TEntity : class, new() +{ + private readonly SqlSugarRepository _rep; + + public BaseService(SqlSugarRepository rep) + { + _rep = rep; + } + + /// + /// 获取详情 🔖 + /// + /// + /// + [DisplayName("获取详情")] + public virtual async Task GetDetail(long id) + { + return await _rep.GetByIdAsync(id); + } + + /// + /// 获取集合 🔖 + /// + /// + [DisplayName("获取集合")] + public virtual async Task> GetList() + { + return await _rep.GetListAsync(); + } + + ///// + ///// 获取实体分页 🔖 + ///// + ///// + ///// + //[ApiDescriptionSettings(Name = "Page")] + //[DisplayName("获取实体分页")] + //public async Task> GetPage([FromQuery] BasePageInput input) + //{ + // return await _rep.AsQueryable().ToPagedListAsync(input.Page, input.PageSize); + //} + + /// + /// 增加 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加")] + public virtual async Task Add(TEntity entity) + { + return await _rep.InsertAsync(entity); + } + + /// + /// 更新 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新")] + public virtual async Task Update(TEntity entity) + { + return await _rep.AsUpdateable(entity).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除")] + public virtual async Task Delete(long id) + { + return await _rep.DeleteByIdAsync(id); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs new file mode 100644 index 00000000..ea6dd916 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs @@ -0,0 +1,281 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using NewLife.Caching.Models; + +namespace Admin.NET.Core.Service; + +/// +/// 系统缓存服务 🧩 +/// +[ApiDescriptionSettings(Order = 400)] +public class SysCacheService : IDynamicApiController, ISingleton +{ + private readonly ICache _cache; + private readonly CacheOptions _cacheOptions; + + public SysCacheService(ICache cache, IOptions cacheOptions) + { + _cache = cache; + _cacheOptions = cacheOptions.Value; + } + + /// + /// 获取缓存键名集合 🔖 + /// + /// + [DisplayName("获取缓存键名集合")] + public List GetKeyList() + { + return _cache == Cache.Default + ? _cache.Keys.Where(u => u.StartsWith(_cacheOptions.Prefix)).Select(u => u[_cacheOptions.Prefix.Length..]).OrderBy(u => u).ToList() + : ((FullRedis)_cache).Search($"{_cacheOptions.Prefix}*", int.MaxValue).Select(u => u[_cacheOptions.Prefix.Length..]).OrderBy(u => u).ToList(); + } + + /// + /// 增加缓存 + /// + /// + /// + /// + [NonAction] + public bool Set(string key, object value) + { + if (string.IsNullOrWhiteSpace(key)) return false; + return _cache.Set($"{_cacheOptions.Prefix}{key}", value); + } + + /// + /// 增加缓存并设置过期时间 + /// + /// + /// + /// + /// + [NonAction] + public bool Set(string key, object value, TimeSpan expire) + { + if (string.IsNullOrWhiteSpace(key)) return false; + return _cache.Set($"{_cacheOptions.Prefix}{key}", value, expire); + } + + /// + /// 获取缓存 + /// + /// + /// + /// + [NonAction] + public T Get(string key) + { + return _cache.Get($"{_cacheOptions.Prefix}{key}"); + } + + /// + /// 删除缓存 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除缓存")] + public int Remove(string key) + { + return _cache.Remove($"{_cacheOptions.Prefix}{key}"); + } + + /// + /// 检查缓存是否存在 + /// + /// 键 + /// + [NonAction] + public bool ExistKey(string key) + { + return _cache.ContainsKey($"{_cacheOptions.Prefix}{key}"); + } + + /// + /// 根据键名前缀删除缓存 🔖 + /// + /// 键名前缀 + /// + [ApiDescriptionSettings(Name = "DeleteByPreKey"), HttpPost] + [DisplayName("根据键名前缀删除缓存")] + public int RemoveByPrefixKey(string prefixKey) + { + var delKeys = _cache == Cache.Default + ? _cache.Keys.Where(u => u.StartsWith($"{_cacheOptions.Prefix}{prefixKey}")).ToArray() + : ((FullRedis)_cache).Search($"{_cacheOptions.Prefix}{prefixKey}*", int.MaxValue).ToArray(); + return _cache.Remove(delKeys); + } + + /// + /// 根据键名前缀获取键名集合 🔖 + /// + /// 键名前缀 + /// + [DisplayName("根据键名前缀获取键名集合")] + public List GetKeysByPrefixKey(string prefixKey) + { + return _cache == Cache.Default + ? _cache.Keys.Where(u => u.StartsWith($"{_cacheOptions.Prefix}{prefixKey}")).Select(u => u[_cacheOptions.Prefix.Length..]).ToList() + : ((FullRedis)_cache).Search($"{_cacheOptions.Prefix}{prefixKey}*", int.MaxValue).Select(u => u[_cacheOptions.Prefix.Length..]).ToList(); + } + + /// + /// 获取缓存值 🔖 + /// + /// + /// + [DisplayName("获取缓存值")] + public object GetValue(string key) + { + return _cache == Cache.Default + ? _cache.Get($"{_cacheOptions.Prefix}{key}") + : _cache.Get($"{_cacheOptions.Prefix}{key}"); + } + + /// + /// 获取或添加缓存(在数据不存在时执行委托请求数据) + /// + /// + /// + /// + /// 过期时间,单位秒 + /// + [NonAction] + public T GetOrAdd(string key, Func callback, int expire = -1) + { + if (string.IsNullOrWhiteSpace(key)) return default; + return _cache.GetOrAdd($"{_cacheOptions.Prefix}{key}", callback, expire); + } + + /// + /// Hash匹配 + /// + /// + /// + /// + [NonAction] + public RedisHash GetHashMap(string key) + { + return _cache.GetDictionary(key) as RedisHash; + } + + /// + /// 批量添加HASH + /// + /// + /// + /// + /// + [NonAction] + public bool HashSet(string key, Dictionary dic) + { + var hash = GetHashMap(key); + return hash.HMSet(dic); + } + + /// + /// 添加一条HASH + /// + /// + /// + /// + /// + [NonAction] + public void HashAdd(string key, string hashKey, T value) + { + var hash = GetHashMap(key); + hash.Add(hashKey, value); + } + + /// + /// 获取多条HASH + /// + /// + /// + /// + /// + [NonAction] + public List HashGet(string key, params string[] fields) + { + var hash = GetHashMap(key); + var result = hash.HMGet(fields); + return result.ToList(); + } + + /// + /// 获取一条HASH + /// + /// + /// + /// + /// + [NonAction] + public T HashGetOne(string key, string field) + { + var hash = GetHashMap(key); + var result = hash.HMGet(new string[] { field }); + return result[0]; + } + + /// + /// 根据KEY获取所有HASH + /// + /// + /// + /// + [NonAction] + public IDictionary HashGetAll(string key) + { + var hash = GetHashMap(key); + return hash.GetAll(); + } + + /// + /// 删除HASH + /// + /// + /// + /// + /// + [NonAction] + public int HashDel(string key, params string[] fields) + { + var hash = GetHashMap(key); + return hash.HDel(fields); + } + + /// + /// 搜索HASH + /// + /// + /// + /// + /// + [NonAction] + public List> HashSearch(string key, SearchModel searchModel) + { + var hash = GetHashMap(key); + return hash.Search(searchModel).ToList(); + } + + /// + /// 搜索HASH + /// + /// + /// + /// + /// + /// + [NonAction] + public List> HashSearch(string key, string pattern, int count) + { + var hash = GetHashMap(key); + return hash.Search(pattern, count).ToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs new file mode 100644 index 00000000..d202e728 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs @@ -0,0 +1,100 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class CustomViewEngine : ViewEngineModel +{ + private readonly ISqlSugarClient _db; + + public CustomViewEngine() + { + } + + public CustomViewEngine(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 库定位器 + /// + public string ConfigId { get; set; } = SqlSugarConst.MainConfigId; + + public string AuthorName { get; set; } + + public string BusName { get; set; } + + public string NameSpace { get; set; } + + public string ClassName { get; set; } + + public string ProjectLastName { get; set; } + + public string LowerClassName + { + get + { + return ClassName[..1].ToLower() + ClassName[1..]; // 首字母小写 + } + } + + public string PagePath { get; set; } = "main"; + + public bool IsJoinTable { get; set; } + + public bool IsUpload { get; set; } + + public string PrintType { get; set; } + + public string PrintName { get; set; } + + public List QueryWhetherList { get; set; } + + public List TableField { get; set; } + + private List ColumnList { get; set; } + + public string GetColumnNetType(object tbName, object colName) + { + if (tbName == null || colName == null) return null; + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == ConfigId); + ColumnList = GetColumnListByTableName(tbName.ToString()); + var col = ColumnList.Where(c => (config.DbSettings.EnableUnderLine + ? CodeGenUtil.CamelColumnName(c.ColumnName, Array.Empty()) + : c.ColumnName) == colName.ToString()).FirstOrDefault(); + return col.NetType; + } + + public List GetColumnListByTableName(string tableName) + { + // 多库代码生成切换库 + var provider = _db.AsTenant().GetConnectionScope(ConfigId != SqlSugarConst.MainConfigId ? ConfigId : SqlSugarConst.MainConfigId); + + // 获取实体类型属性 + var entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); + + // 因为ConfigId的表通常也会用到主库的表来做连接,所以这里如果在ConfigId中找不到实体也尝试一下在主库中查找 + if (ConfigId == SqlSugarConst.MainConfigId && entityType == null) return null; + if (ConfigId != SqlSugarConst.MainConfigId) + { + provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId); + entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); + if (entityType == null) return null; + } + + // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) + return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput + { + ColumnName = u.DbColumnName, + ColumnKey = u.IsPrimarykey.ToString(), + DataType = u.DataType.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = u.ColumnDescription + }).ToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenConfig.cs new file mode 100644 index 00000000..0b842d8b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenConfig.cs @@ -0,0 +1,185 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 代码生成详细配置参数 +/// +public class CodeGenConfig +{ + /// + /// 主键Id + /// + public long Id { get; set; } + + /// + /// 代码生成主表ID + /// + public long CodeGenId { get; set; } + + /// + /// 数据库字段名 + /// + public string ColumnName { get; set; } + + /// + /// 实体属性名 + /// + public string PropertyName { get; set; } + + /// + /// 字段数据长度 + /// + public int ColumnLength { get; set; } + + /// + /// 数据库字段名(首字母小写) + /// + public string LowerPropertyName => string.IsNullOrWhiteSpace(PropertyName) ? null : PropertyName[..1].ToLower() + PropertyName[1..]; + + /// + /// 字段描述 + /// + public string ColumnComment { get; set; } + + /// + /// .NET类型 + /// + public string NetType { get; set; } + + /// + /// 作用类型(字典) + /// + public string EffectType { get; set; } + + /// + /// 外键实体名称 + /// + public string FkEntityName { get; set; } + + /// + /// 外键表名称 + /// + public string FkTableName { get; set; } + + /// + /// 外键实体名称(首字母小写) + /// + public string LowerFkEntityName => + string.IsNullOrWhiteSpace(FkEntityName) ? null : FkEntityName[..1].ToLower() + FkEntityName[1..]; + + /// + /// 外键显示字段 + /// + public string FkColumnName { get; set; } + + /// + /// 外键显示字段(首字母小写) + /// + public string LowerFkColumnName => + string.IsNullOrWhiteSpace(FkColumnName) ? null : FkColumnName[..1].ToLower() + FkColumnName[1..]; + + /// + /// 外键显示字段.NET类型 + /// + public string FkColumnNetType { get; set; } + + /// + /// 字典code + /// + public string DictTypeCode { get; set; } + + /// + /// 列表是否缩进(字典) + /// + public string WhetherRetract { get; set; } + + /// + /// 是否必填(字典) + /// + public string WhetherRequired { get; set; } + + /// + /// 是否可排序(字典) + /// + public string WhetherSortable { get; set; } + + /// + /// 是否是查询条件 + /// + public string QueryWhether { get; set; } + + /// + /// 查询方式 + /// + public string QueryType { get; set; } + + /// + /// 列表显示 + /// + public string WhetherTable { get; set; } + + /// + /// 增改 + /// + public string WhetherAddUpdate { get; set; } + + /// + /// 主外键 + /// + public string ColumnKey { get; set; } + + /// + /// 数据库中类型(物理类型) + /// + public string DataType { get; set; } + + /// + /// 是否是通用字段 + /// + public string WhetherCommon { get; set; } + + /// + /// 表的别名 Table as XXX + /// + public string TableNickName + { + get + { + string str = ""; + if (EffectType == "fk") + { + str = LowerFkEntityName + "_FK_" + LowerFkColumnName; + } + else if (EffectType == "Upload") + { + str = "sysFile_FK_" + LowerPropertyName; + } + return str; + } + } + + /// + /// 显示文本字段 + /// + public string DisplayColumn { get; set; } + + /// + /// 选中值字段 + /// + public string ValueColumn { get; set; } + + /// + /// 父级字段 + /// + public string PidColumn { get; set; } + + /// + /// 排序 + /// + public int OrderNo { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs new file mode 100644 index 00000000..f44ff273 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs @@ -0,0 +1,182 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 代码生成参数类 +/// +public class CodeGenInput : BasePageInput +{ + /// + /// 作者姓名 + /// + public virtual string AuthorName { get; set; } + + /// + /// 类名 + /// + public virtual string ClassName { get; set; } + + /// + /// 是否移除表前缀 + /// + public virtual string TablePrefix { get; set; } + + /// + /// 库定位器名 + /// + public virtual string ConfigId { get; set; } + + /// + /// 数据库名(保留字段) + /// + public virtual string DbName { get; set; } + + /// + /// 数据库类型 + /// + public virtual string DbType { get; set; } + + /// + /// 数据库链接 + /// + public virtual string ConnectionString { get; set; } + + /// + /// 生成方式 + /// + public virtual string GenerateType { get; set; } + + /// + /// 数据库表名 + /// + public virtual string TableName { get; set; } + + /// + /// 命名空间 + /// + public virtual string NameSpace { get; set; } + + /// + /// 业务名(业务代码包名称) + /// + public virtual string BusName { get; set; } + + /// + /// 功能名(数据库表名称) + /// + public virtual string TableComment { get; set; } + + /// + /// 菜单应用分类(应用编码) + /// + public virtual string MenuApplication { get; set; } + + /// + /// 是否生成菜单 + /// + public virtual bool GenerateMenu { get; set; } + + /// + /// 菜单父级 + /// + public virtual long? MenuPid { get; set; } + + /// + /// 页面目录 + /// + public virtual string PagePath { get; set; } + + /// + /// 支持打印类型 + /// + public virtual string PrintType { get; set; } + + /// + /// 打印模版名称 + /// + public virtual string PrintName { get; set; } +} + +public class AddCodeGenInput : CodeGenInput +{ + /// + /// 数据库表名 + /// + [Required(ErrorMessage = "数据库表名不能为空")] + public override string TableName { get; set; } + + /// + /// 业务名(业务代码包名称) + /// + [Required(ErrorMessage = "业务名不能为空")] + public override string BusName { get; set; } + + /// + /// 命名空间 + /// + [Required(ErrorMessage = "命名空间不能为空")] + public override string NameSpace { get; set; } + + /// + /// 作者姓名 + /// + [Required(ErrorMessage = "作者姓名不能为空")] + public override string AuthorName { get; set; } + + ///// + ///// 类名 + ///// + //[Required(ErrorMessage = "类名不能为空")] + //public override string ClassName { get; set; } + + ///// + ///// 是否移除表前缀 + ///// + //[Required(ErrorMessage = "是否移除表前缀不能为空")] + //public override string TablePrefix { get; set; } + + /// + /// 生成方式 + /// + [Required(ErrorMessage = "生成方式不能为空")] + public override string GenerateType { get; set; } + + ///// + ///// 功能名(数据库表名称) + ///// + //[Required(ErrorMessage = "数据库表名不能为空")] + //public override string TableComment { get; set; } + + /// + /// 是否生成菜单 + /// + [Required(ErrorMessage = "是否生成菜单不能为空")] + public override bool GenerateMenu { get; set; } +} + +public class DeleteCodeGenInput +{ + /// + /// 代码生成器Id + /// + [Required(ErrorMessage = "代码生成器Id不能为空")] + public long Id { get; set; } +} + +public class UpdateCodeGenInput : CodeGenInput +{ + /// + /// 代码生成器Id + /// + [Required(ErrorMessage = "代码生成器Id不能为空")] + public long Id { get; set; } +} + +public class QueryCodeGenInput : DeleteCodeGenInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs new file mode 100644 index 00000000..aee96f67 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs @@ -0,0 +1,83 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 代码生成参数类 +/// +public class CodeGenOutput +{ + /// + /// 代码生成器Id + /// + public long Id { get; set; } + + /// + /// 作者姓名 + /// + public string AuthorName { get; set; } + + /// + /// 类名 + /// + public string ClassName { get; set; } + + /// + /// 是否移除表前缀 + /// + public string TablePrefix { get; set; } + + /// + /// 生成方式 + /// + public string GenerateType { get; set; } + + /// + /// 数据库表名 + /// + public string TableName { get; set; } + + /// + /// 包名 + /// + public string PackageName { get; set; } + + /// + /// 业务名(业务代码包名称) + /// + public string BusName { get; set; } + + /// + /// 功能名(数据库表名称) + /// + public string TableComment { get; set; } + + /// + /// 菜单应用分类(应用编码) + /// + public string MenuApplication { get; set; } + + /// + /// 是否生成菜单 + /// + public bool GenerateMenu { get; set; } + + /// + /// 菜单父级 + /// + public long? MenuPid { get; set; } + + /// + /// 支持打印类型 + /// + public string PrintType { get; set; } + + /// + /// 打印模版名称 + /// + public string PrintName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/ColumnOuput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/ColumnOuput.cs new file mode 100644 index 00000000..6b3e88d2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/ColumnOuput.cs @@ -0,0 +1,58 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 数据库表列 +/// +public class ColumnOuput +{ + /// + /// 字段名 + /// + public string ColumnName { get; set; } + + /// + /// 实体的Property名 + /// + public string PropertyName { get; set; } + + /// + /// 字段数据长度 + /// + public int ColumnLength { get; set; } + + /// + /// 数据库中类型 + /// + public string DataType { get; set; } + + /// + /// 是否为主键 + /// + public bool IsPrimarykey { get; set; } + + /// + /// 是否允许为空 + /// + public bool IsNullable { get; set; } + + /// + /// .NET字段类型 + /// + public string NetType { get; set; } + + /// + /// 字段描述 + /// + public string ColumnComment { get; set; } + + /// + /// 主外键 + /// + public string ColumnKey { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/DatabaseOutput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/DatabaseOutput.cs new file mode 100644 index 00000000..5f6d81c3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/DatabaseOutput.cs @@ -0,0 +1,28 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 数据库 +/// +public class DatabaseOutput +{ + /// + /// 库定位器名 + /// + public string ConfigId { get; set; } + + /// + /// 数据库类型 + /// + public SqlSugar.DbType DbType { get; set; } + + /// + /// 数据库连接字符串 + /// + public string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs new file mode 100644 index 00000000..1bb8e81a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 数据库表 +/// +public class TableOutput +{ + /// + /// 库定位器名 + /// + public string ConfigId { get; set; } + + /// + /// 表名(字母形式的) + /// + public string TableName { get; set; } + + /// + /// 实体名称 + /// + public string EntityName { get; set; } + + /// + /// 创建时间 + /// + public string CreateTime { get; set; } + + /// + /// 更新时间 + /// + public string UpdateTime { get; set; } + + /// + /// 表名称描述(功能名) + /// + public string TableComment { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs new file mode 100644 index 00000000..ea6f8aca --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs @@ -0,0 +1,153 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统代码生成配置服务 🧩 +/// +[ApiDescriptionSettings(Order = 260)] +public class SysCodeGenConfigService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + + public SysCodeGenConfigService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取代码生成配置列表 🔖 + /// + /// + /// + [DisplayName("获取代码生成配置列表")] + public async Task> GetList([FromQuery] CodeGenConfig input) + { + return await _db.Queryable() + .Where(u => u.CodeGenId == input.CodeGenId) + .Select() + .Mapper(u => + { + u.NetType = (u.EffectType == "EnumSelector" || u.EffectType == "ConstSelector" ? u.DictTypeCode : u.NetType); + }) + .OrderBy(u => u.OrderNo) + .ToListAsync(); + } + + /// + /// 更新代码生成配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新代码生成配置")] + public async Task UpdateCodeGenConfig(List inputList) + { + if (inputList == null || inputList.Count < 1) return; + await _db.Updateable(inputList.Adapt>()) + .IgnoreColumns(u => new { u.ColumnLength, u.ColumnName, u.PropertyName }) + .ExecuteCommandAsync(); + } + + /// + /// 删除代码生成配置 + /// + /// + /// + [NonAction] + public async Task DeleteCodeGenConfig(long codeGenId) + { + await _db.Deleteable().Where(u => u.CodeGenId == codeGenId).ExecuteCommandAsync(); + } + + /// + /// 获取代码生成配置详情 🔖 + /// + /// + /// + [DisplayName("获取代码生成配置详情")] + public async Task GetDetail([FromQuery] CodeGenConfig input) + { + return await _db.Queryable().FirstAsync(u => u.Id == input.Id); + } + + /// + /// 批量增加代码生成配置 + /// + /// + /// + [NonAction] + public void AddList(List tableColumnOutputList, SysCodeGen codeGenerate) + { + if (tableColumnOutputList == null) return; + + var codeGenConfigs = new List(); + var orderNo = 100; + foreach (var tableColumn in tableColumnOutputList) + { + var codeGenConfig = new SysCodeGenConfig(); + + var YesOrNo = YesNoEnum.Y.ToString(); + if (Convert.ToBoolean(tableColumn.ColumnKey)) + { + YesOrNo = YesNoEnum.N.ToString(); + } + + if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName)) + { + codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString(); + YesOrNo = YesNoEnum.N.ToString(); + } + else + { + codeGenConfig.WhetherCommon = YesNoEnum.N.ToString(); + } + + codeGenConfig.CodeGenId = codeGenerate.Id; + codeGenConfig.ColumnName = tableColumn.ColumnName; // 字段名 + codeGenConfig.PropertyName = tableColumn.PropertyName;// 实体属性名 + codeGenConfig.ColumnLength = tableColumn.ColumnLength;// 长度 + codeGenConfig.ColumnComment = tableColumn.ColumnComment; + codeGenConfig.NetType = tableColumn.DataType; + codeGenConfig.WhetherRetract = YesNoEnum.N.ToString(); + + // 生成代码时,主键并不是必要输入项,故一定要排除主键字段 + codeGenConfig.WhetherRequired = (tableColumn.IsNullable || tableColumn.IsPrimarykey) ? YesNoEnum.N.ToString() : YesNoEnum.Y.ToString(); + codeGenConfig.QueryWhether = YesOrNo; + codeGenConfig.WhetherAddUpdate = YesOrNo; + codeGenConfig.WhetherTable = YesOrNo; + + codeGenConfig.ColumnKey = tableColumn.ColumnKey; + + codeGenConfig.DataType = tableColumn.DataType; + codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType); + codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString(); + codeGenConfig.OrderNo = orderNo; + codeGenConfigs.Add(codeGenConfig); + + orderNo += 10; // 每个配置排序间隔10 + } + // 多库代码生成---这里要切回主库 + var provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId); + provider.Insertable(codeGenConfigs).ExecuteCommand(); + } + + /// + /// 默认查询类型 + /// + /// + /// + private static string GetDefaultQueryType(SysCodeGenConfig codeGenConfig) + { + return (codeGenConfig.NetType?.TrimEnd('?')) switch + { + "string" => "like", + "DateTime" => "~", + _ => "==", + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs new file mode 100644 index 00000000..61751ba5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs @@ -0,0 +1,806 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.IO.Compression; + +namespace Admin.NET.Core.Service; + +/// +/// 系统代码生成器服务 🧩 +/// +[ApiDescriptionSettings(Order = 270)] +public class SysCodeGenService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + + private readonly SysCodeGenConfigService _codeGenConfigService; + private readonly IViewEngine _viewEngine; + private readonly CodeGenOptions _codeGenOptions; + + public SysCodeGenService(ISqlSugarClient db, + SysCodeGenConfigService codeGenConfigService, + IViewEngine viewEngine, + IOptions codeGenOptions) + { + _db = db; + _codeGenConfigService = codeGenConfigService; + _viewEngine = viewEngine; + _codeGenOptions = codeGenOptions.Value; + } + + /// + /// 获取代码生成分页列表 🔖 + /// + /// + /// + [DisplayName("获取代码生成分页列表")] + public async Task> Page(CodeGenInput input) + { + return await _db.Queryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.TableName), u => u.TableName.Contains(input.TableName.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.BusName), u => u.BusName.Contains(input.BusName.Trim())) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加代码生成")] + public async Task AddCodeGen(AddCodeGenInput input) + { + var isExist = await _db.Queryable().Where(u => u.TableName == input.TableName).AnyAsync(); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1400); + + var codeGen = input.Adapt(); + var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync(); + // 加入配置表中 + _codeGenConfigService.AddList(GetColumnList(input), newCodeGen); + } + + /// + /// 更新代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新代码生成")] + public async Task UpdateCodeGen(UpdateCodeGenInput input) + { + var isExist = await _db.Queryable().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1400); + + await _db.Updateable(input.Adapt()).ExecuteCommandAsync(); + } + + /// + /// 删除代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除代码生成")] + public async Task DeleteCodeGen(List inputs) + { + if (inputs == null || inputs.Count < 1) return; + + var codeGenConfigTaskList = new List(); + inputs.ForEach(u => + { + _db.Deleteable().In(u.Id).ExecuteCommand(); + + // 删除配置表中 + codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id)); + }); + await Task.WhenAll(codeGenConfigTaskList); + } + + /// + /// 获取代码生成详情 🔖 + /// + /// + /// + [DisplayName("获取代码生成详情")] + public async Task GetDetail([FromQuery] QueryCodeGenInput input) + { + return await _db.Queryable().SingleAsync(u => u.Id == input.Id); + } + + /// + /// 获取数据库库集合 🔖 + /// + /// + [DisplayName("获取数据库库集合")] + public async Task> GetDatabaseList() + { + var dbConfigs = App.GetOptions().ConnectionConfigs; + return await Task.FromResult(dbConfigs.Adapt>()); + } + + /// + /// 获取数据库表(实体)集合 🔖 + /// + /// + [DisplayName("获取数据库表(实体)集合")] + public async Task> GetTableList(string configId = SqlSugarConst.MainConfigId) + { + var provider = _db.AsTenant().GetConnectionScope(configId); + var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用 + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId)); + + var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList(); + IEnumerable entityInfos = await GetEntityInfos(); + + var tableOutputList = new List(); + foreach (var item in entityInfos) + { + var table = dbTableInfos.FirstOrDefault(u => u.Name.ToLower() == (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()); + if (table == null) continue; + tableOutputList.Add(new TableOutput + { + ConfigId = configId, + EntityName = item.EntityName, + TableName = table.Name, + TableComment = item.TableDescription + }); + } + return tableOutputList; + } + + /// + /// 根据表名获取列集合 🔖 + /// + /// + [DisplayName("根据表名获取列集合")] + public List GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId) + { + // 切库---多库代码生成用 + var provider = _db.AsTenant().GetConnectionScope(configId); + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId); + // 获取实体类型属性 + var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName); + if (entityType == null) return null; + var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; + // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) + return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput + { + ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, + ColumnKey = u.IsPrimarykey.ToString(), + DataType = u.DataType.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = u.ColumnDescription + }).ToList(); + } + + /// + /// 获取数据表列(实体属性)集合 + /// + /// + private List GetColumnList([FromQuery] AddCodeGenInput input) + { + var entityType = GetEntityInfos().GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName); + if (entityType == null) + return null; + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + var dbTableName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(entityType.DbTableName) : entityType.DbTableName; + + // 切库---多库代码生成用 + var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId); + + var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; + var columnInfos = provider.DbMaintenance.GetColumnInfosByTableName(dbTableName, false); + var result = columnInfos.Select(u => new ColumnOuput + { + // 转下划线后的列名需要再转回来(暂时不转) + //ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, + ColumnName = u.DbColumnName, + ColumnLength = u.Length, + IsPrimarykey = u.IsPrimarykey, + IsNullable = u.IsNullable, + ColumnKey = u.IsPrimarykey.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? u.DbColumnName : u.ColumnDescription + }).ToList(); + + // 获取实体的属性信息,赋值给PropertyName属性(CodeFirst模式应以PropertyName为实际使用名称) + var entityProperties = entityType.Type.GetProperties(); + + for (int i = result.Count - 1; i >= 0; i--) + { + var columnOutput = result[i]; + // 先找自定义字段名的,如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性) + var propertyInfo = entityProperties.FirstOrDefault(u => (u.GetCustomAttribute()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower()) ?? + entityProperties.FirstOrDefault(u => u.GetCustomAttribute() != null && u.Name.ToLower() == (config.DbSettings.EnableUnderLine + ? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower() + : columnOutput.ColumnName.ToLower())); + if (propertyInfo != null) + { + columnOutput.PropertyName = propertyInfo.Name; + columnOutput.ColumnComment = propertyInfo.GetCustomAttribute().ColumnDescription; + } + else + { + result.RemoveAt(i); // 移除没有定义此属性的字段 + } + } + return result; + } + + /// + /// 获取库表信息 + /// + /// + private async Task> GetEntityInfos() + { + var entityInfos = new List(); + + var type = typeof(SugarTable); + var types = new List(); + if (_codeGenOptions.EntityAssemblyNames != null) + { + foreach (var assemblyName in _codeGenOptions.EntityAssemblyNames) + { + Assembly asm = Assembly.Load(assemblyName); + types.AddRange(asm.GetExportedTypes().ToList()); + } + } + bool IsMyAttribute(Attribute[] o) + { + foreach (Attribute a in o) + { + if (a.GetType() == type) + return true; + } + return false; + } + Type[] cosType = types.Where(o => + { + return IsMyAttribute(Attribute.GetCustomAttributes(o, true)); + } + ).ToArray(); + + foreach (var ct in cosType) + { + var sugarAttribute = ct.GetCustomAttributes(type, true)?.FirstOrDefault(); + + var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), true); + var description = ""; + if (des.Length > 0) + { + description = ((DescriptionAttribute)des[0]).Description; + } + entityInfos.Add(new EntityInfo() + { + EntityName = ct.Name, + DbTableName = sugarAttribute == null ? ct.Name : ((SugarTable)sugarAttribute).TableName, + TableDescription = sugarAttribute == null ? description : ((SugarTable)sugarAttribute).TableDescription, + Type = ct + }); + } + return await Task.FromResult(entityInfos); + } + + /// + /// 获取程序保存位置 🔖 + /// + /// + [DisplayName("获取程序保存位置")] + public List GetApplicationNamespaces() + { + return _codeGenOptions.BackendApplicationNamespaces; + } + + /// + /// 代码生成到本地 🔖 + /// + /// + [DisplayName("代码生成到本地")] + public async Task RunLocal(SysCodeGen input) + { + if (string.IsNullOrEmpty(input.GenerateType)) + input.GenerateType = "200"; + + // 先删除该表已生成的菜单列表 + List targetPathList; + var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); + if (input.GenerateType.StartsWith('1')) + { + targetPathList = GetZipPathList(input); + if (Directory.Exists(zipPath)) + Directory.Delete(zipPath, true); + } + else + targetPathList = GetTargetPathList(input); + + var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 + var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 + var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 + (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 + + var data = new CustomViewEngine(_db) + { + ConfigId = input.ConfigId, + AuthorName = input.AuthorName, + BusName = input.BusName, + NameSpace = input.NameSpace, + ClassName = input.TableName, + PagePath = input.PagePath, + ProjectLastName = input.NameSpace.Split('.').Last(), + QueryWhetherList = queryWhetherList, + TableField = tableFieldList, + IsJoinTable = joinTableList.Count > 0, + IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), + PrintType = input.PrintType, + PrintName = input.PrintName, + }; + //模板目录 + var templatePathList = GetTemplatePathList(input); + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + + for (var i = 0; i < templatePathList.Count; i++) + { + var templateFilePath = Path.Combine(templatePath, templatePathList[i]); + if (!File.Exists(templateFilePath)) continue; + var tContent = File.ReadAllText(templateFilePath); + var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => + { + builder.AddAssemblyReferenceByName("System.Linq"); + builder.AddAssemblyReferenceByName("System.Collections"); + builder.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + var dirPath = new DirectoryInfo(targetPathList[i]).Parent.FullName; + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + File.WriteAllText(targetPathList[i], tResult, Encoding.UTF8); + } + if (input.GenerateMenu) + await AddMenu(input.TableName, input.BusName, input.MenuPid ?? 0, input.MenuIcon, input.PagePath, tableFieldList); + // 非ZIP压缩返回空 + if (!input.GenerateType.StartsWith('1')) + return null; + else + { + string downloadPath = zipPath + ".zip"; + // 判断是否存在同名称文件 + if (File.Exists(downloadPath)) + File.Delete(downloadPath); + ZipFile.CreateFromDirectory(zipPath, downloadPath); + return new { url = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}/CodeGen/{input.TableName}.zip" }; + } + } + + /// + /// 获取代码生成预览 🔖 + /// + /// + [DisplayName("获取代码生成预览")] + public async Task> Preview(SysCodeGen input) + { + var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 + var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 + var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 + (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 + + var data = new CustomViewEngine(_db) + { + ConfigId = input.ConfigId, + AuthorName = input.AuthorName, + BusName = input.BusName, + NameSpace = input.NameSpace, + ClassName = input.TableName, + PagePath = input.PagePath, + ProjectLastName = input.NameSpace.Split('.').Last(), + QueryWhetherList = queryWhetherList, + TableField = tableFieldList, + IsJoinTable = joinTableList.Count > 0, + IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), + PrintType = input.PrintType, + PrintName = input.PrintName, + }; + + // 获取模板文件并替换 + var templatePathList = GetTemplatePathList(); + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + + var result = new Dictionary(); + for (var i = 0; i < templatePathList.Count; i++) + { + var templateFilePath = Path.Combine(templatePath, templatePathList[i]); + if (!File.Exists(templateFilePath)) continue; + var tContent = File.ReadAllText(templateFilePath); + var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => + { + builder.AddAssemblyReferenceByName("System.Linq"); + builder.AddAssemblyReferenceByName("System.Collections"); + builder.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + result.Add(templatePathList[i]?.TrimEnd(".vm"), tResult); + } + + return result; + } + + /// + /// 获取连表的实体名和别名 + /// + /// + /// + private static (string, string) GetJoinTableStr(List configs) + { + var uploads = configs.Where(u => u.EffectType == "Upload").ToList(); + var fks = configs.Where(u => u.EffectType == "fk").ToList(); + string str = ""; // + string lowerStr = ""; // (o, i, c) + foreach (var item in uploads) + { + lowerStr += "sysFile_FK_" + item.LowerPropertyName + ","; + str += "SysFile,"; + } + foreach (var item in fks) + { + lowerStr += item.LowerFkEntityName + "_FK_" + item.LowerFkColumnName + ","; + str += item.FkEntityName + ","; + } + return (str.TrimEnd(','), lowerStr.TrimEnd(',')); + } + + /// + /// 增加菜单 + /// + /// + /// + /// + /// + /// + /// + /// + private async Task AddMenu(string className, string busName, long pid, string menuIcon, string pagePath, List tableFieldList) + { + var pPath = string.Empty; + // 若 pid=0 为顶级则创建菜单目录 + if (pid == 0) + { + // 目录 + var menuType0 = new SysMenu + { + Pid = 0, + Title = busName + "管理", + Type = MenuTypeEnum.Dir, + Icon = "robot", + Path = "/" + className.ToLower(), + Component = "Layout", + }; + // 若先前存在则删除本级和下级 + var menuList0 = await _db.Queryable().Where(u => u.Title == menuType0.Title && u.Type == menuType0.Type).ToListAsync(); + if (menuList0.Count > 0) + { + var listIds = menuList0.Select(u => u.Id).ToList(); + var childlistIds = new List(); + foreach (var item in listIds) + { + var childlist = await _db.Queryable().ToChildListAsync(u => u.Pid, item); + childlistIds.AddRange(childlist.Select(u => u.Id).ToList()); + } + listIds.AddRange(childlistIds); + await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); + await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); + } + pid = (await _db.Insertable(menuType0).ExecuteReturnEntityAsync()).Id; + } + else + { + var pMenu = await _db.Queryable().FirstAsync(u => u.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505); + pPath = pMenu.Path; + } + + // 菜单 + var menuType = new SysMenu + { + Pid = pid, + Title = busName + "管理", + Name = className[..1].ToLower() + className[1..], + Type = MenuTypeEnum.Menu, + Icon = menuIcon, + Path = pPath + "/" + className.ToLower(), + Component = "/" + pagePath + "/" + className[..1].ToLower() + className[1..] + "/index", + }; + // 若先前存在则删除本级和下级 + var menuListCurrent = await _db.Queryable().Where(u => u.Title == menuType.Title && u.Type == menuType.Type).ToListAsync(); + if (menuListCurrent.Count > 0) + { + var listIds = menuListCurrent.Select(u => u.Id).ToList(); + var childListIds = new List(); + foreach (var item in listIds) + { + var childList = await _db.Queryable().ToChildListAsync(u => u.Pid, item); + childListIds.AddRange(childList.Select(u => u.Id).ToList()); + } + listIds.AddRange(childListIds); + await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); + await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); + } + + var menuPid = (await _db.Insertable(menuType).ExecuteReturnEntityAsync()).Id; + int menuOrder = 100; + // 按钮-page + var menuTypePage = new SysMenu + { + Pid = menuPid, + Title = "查询", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":page", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-detail + var menuTypeDetail = new SysMenu + { + Pid = menuPid, + Title = "详情", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":detail", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-add + var menuTypeAdd = new SysMenu + { + Pid = menuPid, + Title = "增加", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":add", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-delete + var menuTypeDelete = new SysMenu + { + Pid = menuPid, + Title = "删除", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":delete", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-update + var menuTypeUpdate = new SysMenu + { + Pid = menuPid, + Title = "编辑", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":update", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-print + var menuTypePrint = new SysMenu + { + Pid = menuPid, + Title = "打印", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":print", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-import + var menuTypeImport = new SysMenu + { + Pid = menuPid, + Title = "导入", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":import", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-export + var menuTypeExport = new SysMenu + { + Pid = menuPid, + Title = "导出", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":export", + OrderNo = menuOrder + }; + menuOrder += 10; + + var menuList = new List() { menuTypePage, menuTypeDetail, menuTypeAdd, menuTypeDelete, menuTypeUpdate, menuTypePrint, menuTypeImport, menuTypeExport }; + // 加入fk、Upload、ApiTreeSelect 等接口的权限 + // 在生成表格时,有些字段只是查询时显示,不需要填写(WhetherAddUpdate),所以这些字段没必要生成相应接口 + var fkTableList = tableFieldList.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList(); + foreach (var @column in fkTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "外键" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + column.ColumnName + "Dropdown", + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelect").ToList(); + foreach (var @column in treeSelectTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "树型" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + "Tree", + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + var uploadTableList = tableFieldList.Where(u => u.EffectType == "Upload").ToList(); + foreach (var @column in uploadTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "上传" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":Upload" + column.ColumnName, + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + await _db.Insertable(menuList).ExecuteCommandAsync(); + } + + /// + /// 获取模板文件路径集合 + /// + /// + private static List GetTemplatePathList(SysCodeGen input) + { + if (input.GenerateType.Substring(1, 1).Contains('1')) + { + return new() { "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + } + else if (input.GenerateType.Substring(1, 1).Contains('2')) + { + return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm" }; + } + else + { + return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + } + } + + /// + /// 获取模板文件路径集合 + /// + /// + private static List GetTemplatePathList() => new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + + /// + /// 设置生成文件路径 + /// + /// + /// + private List GetTargetPathList(SysCodeGen input) + { + //var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Service", input.TableName); + var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); + var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); + var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); + var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); + var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); + var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");// + var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); + var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); + + if (input.GenerateType.Substring(1, 1).Contains('1')) + { + // 生成到本项目(前端) + return new List() + { + indexPath, + formModalPath, + apiJsPath + }; + } + else if (input.GenerateType.Substring(1, 1).Contains('2')) + { + // 生成到本项目(后端) + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + }; + } + else + { + // 前后端同时生成到本项目 + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + indexPath, + formModalPath, + apiJsPath + }; + } + } + + /// + /// 设置生成文件路径 + /// + /// + /// + private List GetZipPathList(SysCodeGen input) + { + var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); + + //var backendPath = Path.Combine(zipPath, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); + var backendPath = Path.Combine(zipPath, input.NameSpace, "Service", input.TableName); + var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); + var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); + var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); + var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); + var frontendPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); + var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue"); + var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); + var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); + if (input.GenerateType.StartsWith("11")) + { + return new List() + { + indexPath, + formModalPath, + apiJsPath + }; + } + else if (input.GenerateType.StartsWith("12")) + { + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath + }; + } + else + { + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + indexPath, + formModalPath, + apiJsPath + }; + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/Dto/ApiOutput.cs b/Admin.NET/Admin.NET.Core/Service/Common/Dto/ApiOutput.cs new file mode 100644 index 00000000..c2988c2b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Common/Dto/ApiOutput.cs @@ -0,0 +1,59 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 接口/动态API输出 +/// +public class ApiOutput +{ + /// + /// 组名称 + /// + public string GroupName { get; set; } + + /// + /// 接口列表 + /// + public List Apis { get; set; } = new List(); +} + +/// +/// 接口/动态API信息 +/// +public class ApiInfo +{ + /// + /// 接口名称 + /// + public string DisplayName { get; set; } + + /// + /// 路由 + /// + public string Route { get; set; } + + /// + /// 请求方式 + /// + public string HttpMethod { get; set; } + + /// + /// 控制器名称 + /// + public string ControllerName { get; set; } + + /// + /// 方法名称 + /// + public string ActionName { get; set; } + + ///// + ///// 组名称 + ///// + //public string GroupName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/Dto/ProcDto.cs b/Admin.NET/Admin.NET.Core/Service/Common/Dto/ProcDto.cs new file mode 100644 index 00000000..6acbea63 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Common/Dto/ProcDto.cs @@ -0,0 +1,90 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Magicodes.ExporterAndImporter.Core.Filters; +using Magicodes.ExporterAndImporter.Core.Models; + +namespace Admin.NET.Core.Service; + +/// +/// 基础存储过程输入类 +/// +public class BaseProcInput +{ + /// + /// ProcId + /// + public string ProcId { get; set; } + + /// + /// 数据库配置Id + /// + public string ConfigId { get; set; } = SqlSugarConst.MainConfigId; + + /// + /// 存储过程输入参数 + /// + /// {"id":"351060822794565"} + public Dictionary ProcParams { get; set; } +} + +/// +/// 带表头名称存储过程输入类 +/// +public class ExportProcByTMPInput : BaseProcInput +{ + /// + /// 模板名称 + /// + public string Template { get; set; } +} + +/// +/// 带表头名称存储过程输入类 +/// +public class ExportProcInput : BaseProcInput +{ + public Dictionary EHeader { get; set; } +} + +/// +/// 指定导出类名(有排序)存储过程输入类 +/// +public class ExportProcInput2 : BaseProcInput +{ + public List EHeader { get; set; } +} + +/// +/// 前端指定列 +/// +public class ProcExporterHeaderFilter : IExporterHeaderFilter +{ + private Dictionary> _includeHeader; + + public ProcExporterHeaderFilter(Dictionary> includeHeader) + { + _includeHeader = includeHeader; + } + + public ExporterHeaderInfo Filter(ExporterHeaderInfo exporterHeaderInfo) + { + if (_includeHeader != null && _includeHeader.Count > 0) + { + var key = exporterHeaderInfo.PropertyName.ToUpper(); + if (_includeHeader.ContainsKey(key)) + { + exporterHeaderInfo.DisplayName = _includeHeader[key].Item1; + return exporterHeaderInfo; + } + else + { + exporterHeaderInfo.ExporterHeaderAttribute.Hidden = true; + } + } + return exporterHeaderInfo; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/Dto/SmKeyPairOutput.cs b/Admin.NET/Admin.NET.Core/Service/Common/Dto/SmKeyPairOutput.cs new file mode 100644 index 00000000..cc97d39c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Common/Dto/SmKeyPairOutput.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 国密公钥私钥对输出 +/// +public class SmKeyPairOutput +{ + /// + /// 私匙 + /// + public string PrivateKey { get; set; } + + /// + /// 公匙 + /// + public string PublicKey { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs new file mode 100644 index 00000000..63112e4d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs @@ -0,0 +1,112 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Admin.NET.Core.Service; + +/// +/// 系统通用服务 🧩 +/// +[ApiDescriptionSettings(Order = 101)] +[AllowAnonymous] +public class SysCommonService : IDynamicApiController, ITransient +{ + private readonly IApiDescriptionGroupCollectionProvider _apiProvider; + + public SysCommonService(IApiDescriptionGroupCollectionProvider apiProvider) + { + _apiProvider = apiProvider; + } + + /// + /// 获取国密公钥私钥对 🏆 + /// + /// + [DisplayName("获取国密公钥私钥对")] + public SmKeyPairOutput GetSmKeyPair() + { + var kp = GM.GenerateKeyPair(); + var privateKey = Hex.ToHexString(((ECPrivateKeyParameters)kp.Private).D.ToByteArray()).ToUpper(); + var publicKey = Hex.ToHexString(((ECPublicKeyParameters)kp.Public).Q.GetEncoded()).ToUpper(); + + return new SmKeyPairOutput + { + PrivateKey = privateKey, + PublicKey = publicKey, + }; + } + + /// + /// 获取MD5加密字符串 🏆 + /// + /// + /// + /// + [DisplayName("获取MD5加密字符串")] + public string GetMD5Encrypt(string text, bool uppercase = false) + { + return MD5Encryption.Encrypt(text, uppercase, is16: false); + } + + /// + /// 获取所有接口/动态API 🔖 + /// + /// + /// + [DisplayName("获取所有接口/动态API")] + public List GetApiList([FromQuery] string groupName = "") + { + var apiList = new List(); + + //// 路由前缀 + //var defaultRoutePrefix = App.GetOptions().DefaultRoutePrefix; + + // 获取所有接口分组 + var apiDescriptionGroups = _apiProvider.ApiDescriptionGroups.Items; + foreach (ApiDescriptionGroup group in apiDescriptionGroups) + { + if (!string.IsNullOrWhiteSpace(groupName) && group.GroupName != groupName) + continue; + + var apiOuput = new ApiOutput + { + GroupName = string.IsNullOrWhiteSpace(group.GroupName) ? "系统接口" : group.GroupName + }; + // 获取接口分组的所有方法/接口 + var actions = group.Items; + foreach (ApiDescription action in actions) + { + // 路由名称(去掉参数) + var routeName = action.RelativePath.Contains('{') ? action.RelativePath[..action.RelativePath.IndexOf('{')] : action.RelativePath; + // 去掉路由前缀 + routeName = routeName[(routeName.IndexOf('/') + 1)..]; + + var controllerActionDescriptor = action.ActionDescriptor as ControllerActionDescriptor; + var displayName = controllerActionDescriptor.MethodInfo.GetCustomAttribute(true)?.DisplayName; + + apiOuput.Apis.Add(new ApiInfo + { + DisplayName = displayName, + Route = routeName, + HttpMethod = action.HttpMethod, + ControllerName = controllerActionDescriptor.ControllerName, + ActionName = controllerActionDescriptor.ActionName, + //GroupName = string.IsNullOrWhiteSpace(action.GroupName) ? "Default" : action.GroupName, + }); + + // 按照控制器名称进行排序 + apiOuput.Apis = apiOuput.Apis.OrderBy(u => u.ControllerName).ToList(); + } + + apiList.Add(apiOuput); + } + return apiList; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/SysProcService.cs b/Admin.NET/Admin.NET.Core/Service/Common/SysProcService.cs new file mode 100644 index 00000000..588954a3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Common/SysProcService.cs @@ -0,0 +1,101 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统存储过程服务 🧩 +/// +[ApiDescriptionSettings(Order = 102)] +public class SysProcService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + + public SysProcService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 导出存储过程数据-指定列,没有指定的字段会被隐藏 🔖 + /// + /// + public async Task PocExport2(ExportProcInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var dt = await db.Ado.UseStoredProcedure().GetDataTableAsync(input.ProcId, input.ProcParams); + + var headers = new Dictionary>(); + var index = 1; + foreach (var val in input.EHeader) + { + headers.Add(val.Key.ToUpper(), new Tuple(val.Value, index)); + index++; + } + var excelExporter = new ExcelExporter(); + var da = await excelExporter.ExportAsByteArray(dt, new ProcExporterHeaderFilter(headers)); + return new FileContentResult(da, "application/octet-stream") { FileDownloadName = input.ProcId + ".xlsx" }; + } + + /// + /// 根据模板导出存储过程数据 🔖 + /// + /// + /// + public async Task PocExport(ExportProcByTMPInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var dt = await db.Ado.UseStoredProcedure().GetDataTableAsync(input.ProcId, input.ProcParams); + + var excelExporter = new ExcelExporter(); + string template = AppDomain.CurrentDomain.BaseDirectory + "/wwwroot/Template/" + input.Template + ".xlsx"; + var bs = await excelExporter.ExportBytesByTemplate(dt, template); + return new FileContentResult(bs, "application/octet-stream") { FileDownloadName = input.ProcId + ".xlsx" }; + } + + /// + /// 获取存储过程返回表-Oracle、达梦参数顺序不能错 🔖 + /// + /// + /// + public async Task ProcTable(BaseProcInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + return await db.Ado.UseStoredProcedure().GetDataTableAsync(input.ProcId, input.ProcParams); + } + + /// + /// 获取存储过程返回数据集-Oracle、达梦参数顺序不能错 + /// Oracle 返回table、table1,其他返回table1、table2。适用于报表、复杂详细页面等 🔖 + /// + /// + /// + [HttpPost] + public async Task CommonDataSet(BaseProcInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + return await db.Ado.UseStoredProcedure().GetDataSetAllAsync(input.ProcId, input.ProcParams); + } + + ///// + ///// 根据配置表获取对映存储过程 + ///// + ///// + ///// + //public async Task ProcEnitybyConfig(BaseProcInput input) + //{ + // var key = "ProcConfig"; + // var ds = _sysCacheService.Get>(key); + // if (ds == null || ds.Count == 0 || !ds.ContainsKey(input.ProcId)) + // { + // var datas = await _db.Queryable().ToListAsync(); + // ds = datas.ToDictionary(m => m.ProcId, m => m.ProcName); + // _sysCacheService.Set(key, ds); + // } + // var procName = ds[input.ProcId]; + // return await _db.Ado.UseStoredProcedure().GetDataTableAsync(procName, input.ProcParams); + //} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Config/Dto/ConfigInput.cs b/Admin.NET/Admin.NET.Core/Service/Config/Dto/ConfigInput.cs new file mode 100644 index 00000000..1fdc16b4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Config/Dto/ConfigInput.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class ConfigInput : BaseIdInput +{ +} + +public class PageConfigInput : BasePageInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } + + /// + /// 分组编码 + /// + public string GroupCode { get; set; } +} + +public class AddConfigInput : SysConfig +{ +} + +public class UpdateConfigInput : AddConfigInput +{ +} + +public class DeleteConfigInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs b/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs new file mode 100644 index 00000000..83813159 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统信息保存输入参数 +/// +public class InfoSaveInput +{ + /// + /// 系统图标(Data URI scheme base64 编码) + /// + public string SysLogoBase64 { get; set; } + + /// + /// 系统主标题 + /// + public string SysTitle { get; set; } + + /// + /// 系统副标题 + /// + public string SysViceTitle { get; set; } + + /// + /// 系统描述 + /// + public string SysViceDesc { get; set; } + + /// + /// 水印内容 + /// + public string SysWatermark { get; set; } + + /// + /// 版权说明 + /// + public string SysCopyright { get; set; } + + /// + /// ICP备案号 + /// + public string SysIcp { get; set; } + + /// + /// ICP地址 + /// + public string SysIcpUrl { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs b/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs new file mode 100644 index 00000000..820bb2f8 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs @@ -0,0 +1,291 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统参数配置服务 🧩 +/// +[ApiDescriptionSettings(Order = 440)] +public class SysConfigService : IDynamicApiController, ITransient +{ + private readonly SysCacheService _sysCacheService; + private readonly SqlSugarRepository _sysConfigRep; + + public SysConfigService(SysCacheService sysCacheService, + SqlSugarRepository sysConfigRep) + { + _sysCacheService = sysCacheService; + _sysConfigRep = sysConfigRep; + } + + /// + /// 获取参数配置分页列表 🔖 + /// + /// + /// + [DisplayName("获取参数配置分页列表")] + public async Task> Page(PageConfigInput input) + { + return await _sysConfigRep.AsQueryable() + .Where(u => u.GroupCode != "WebConfig") // 不显示 WebConfig 分组 + .WhereIF(!string.IsNullOrWhiteSpace(input.Name?.Trim()), u => u.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code?.Trim()), u => u.Code.Contains(input.Code)) + .WhereIF(!string.IsNullOrWhiteSpace(input.GroupCode?.Trim()), u => u.GroupCode.Equals(input.GroupCode)) + .OrderBuilder(input) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取参数配置列表 🔖 + /// + /// + [DisplayName("获取参数配置列表")] + public async Task> GetList() + { + return await _sysConfigRep.GetListAsync(); + } + + /// + /// 增加参数配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加参数配置")] + public async Task AddConfig(AddConfigInput input) + { + var isExist = await _sysConfigRep.IsAnyAsync(u => u.Name == input.Name || u.Code == input.Code); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D9000); + + await _sysConfigRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新参数配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新参数配置")] + public async Task UpdateConfig(UpdateConfigInput input) + { + var isExist = await _sysConfigRep.IsAnyAsync(u => (u.Name == input.Name || u.Code == input.Code) && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D9000); + + var config = input.Adapt(); + await _sysConfigRep.AsUpdateable(config).IgnoreColumns(true).ExecuteCommandAsync(); + + _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}"); + } + + /// + /// 删除参数配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除参数配置")] + public async Task DeleteConfig(DeleteConfigInput input) + { + var config = await _sysConfigRep.GetFirstAsync(u => u.Id == input.Id); + if (config.SysFlag == YesNoEnum.Y) // 禁止删除系统参数 + throw Oops.Oh(ErrorCodeEnum.D9001); + + await _sysConfigRep.DeleteAsync(config); + + _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}"); + } + + /// + /// 批量删除参数配置 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost] + [DisplayName("批量删除参数配置")] + public async Task BatchDeleteConfig(List ids) + { + foreach (var id in ids) + { + var config = await _sysConfigRep.GetFirstAsync(u => u.Id == id); + if (config.SysFlag == YesNoEnum.Y) // 禁止删除系统参数 + continue; + + await _sysConfigRep.DeleteAsync(config); + + _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}"); + } + } + + /// + /// 获取参数配置详情 🔖 + /// + /// + /// + [DisplayName("获取参数配置详情")] + public async Task GetDetail([FromQuery] ConfigInput input) + { + return await _sysConfigRep.GetFirstAsync(u => u.Id == input.Id); + } + + /// + /// 获取参数配置值 + /// + /// + /// + [NonAction] + public async Task GetConfigValue(string code) + { + if (string.IsNullOrWhiteSpace(code)) return default; + + var value = _sysCacheService.Get($"{CacheConst.KeyConfig}{code}"); + if (string.IsNullOrEmpty(value)) + { + var config = await _sysConfigRep.GetFirstAsync(u => u.Code == code); + value = config != null ? config.Value : default; + _sysCacheService.Set($"{CacheConst.KeyConfig}{code}", value); + } + if (string.IsNullOrWhiteSpace(value)) return default; + return (T)Convert.ChangeType(value, typeof(T)); + } + + /// + /// 更新参数配置值 + /// + /// + /// + /// + [NonAction] + public async Task UpdateConfigValue(string code, string value) + { + var config = await _sysConfigRep.GetFirstAsync(u => u.Code == code); + if (config == null) return; + + config.Value = value; + await _sysConfigRep.AsUpdateable(config).ExecuteCommandAsync(); + + _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}"); + } + + /// + /// 获取分组列表 🔖 + /// + /// + [DisplayName("获取分组列表")] + public async Task> GetGroupList() + { + return await _sysConfigRep.AsQueryable() + .Where(u => u.GroupCode != "WebConfig") // 不显示 WebConfig 分组 + .GroupBy(u => u.GroupCode) + .Select(u => u.GroupCode).ToListAsync(); + } + + /// + /// 获取 Token 过期时间 + /// + /// + [NonAction] + public async Task GetTokenExpire() + { + var tokenExpireStr = await GetConfigValue(CommonConst.SysTokenExpire); + _ = int.TryParse(tokenExpireStr, out var tokenExpire); + return tokenExpire == 0 ? 20 : tokenExpire; + } + + /// + /// 获取 RefreshToken 过期时间 + /// + /// + [NonAction] + public async Task GetRefreshTokenExpire() + { + var refreshTokenExpireStr = await GetConfigValue(CommonConst.SysRefreshTokenExpire); + _ = int.TryParse(refreshTokenExpireStr, out var refreshTokenExpire); + return refreshTokenExpire == 0 ? 40 : refreshTokenExpire; + } + + /// + /// 获取系统信息 🔖 + /// + /// + [SuppressMonitor] + [AllowAnonymous] + [DisplayName("获取系统信息")] + public async Task GetSysInfo() + { + var sysLogo = await GetConfigValue("sys_web_logo"); + var sysTitle = await GetConfigValue("sys_web_title"); + var sysViceTitle = await GetConfigValue("sys_web_viceTitle"); + var sysViceDesc = await GetConfigValue("sys_web_viceDesc"); + var sysWatermark = await GetConfigValue("sys_web_watermark"); + var sysCopyright = await GetConfigValue("sys_web_copyright"); + var sysIcp = await GetConfigValue("sys_web_icp"); + var sysIcpUrl = await GetConfigValue("sys_web_icpUrl"); + return new + { + SysLogo = sysLogo, + SysTitle = sysTitle, + SysViceTitle = sysViceTitle, + SysViceDesc = sysViceDesc, + SysWatermark = sysWatermark, + SysCopyright = sysCopyright, + SysIcp = sysIcp, + SysIcpUrl = sysIcpUrl + }; + } + + /// + /// 保存系统信息 🔖 + /// + /// + [DisplayName("保存系统信息")] + public async Task SaveSysInfo(InfoSaveInput input) + { + // logo 不为空才保存 + if (!string.IsNullOrEmpty(input.SysLogoBase64)) + { + // 旧图标文件相对路径 + var oldSysLogoRelativeFilePath = await GetConfigValue("sys_web_logo") ?? ""; + var oldSysLogoAbsoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, oldSysLogoRelativeFilePath.TrimStart('/')); + + var groups = Regex.Match(input.SysLogoBase64, @"data:image/(?.+?);base64,(?.+)").Groups; + var type = groups["type"].Value; + var base64Data = groups["data"].Value; + var binData = Convert.FromBase64String(base64Data); + + // 本地图标保存路径 + var path = "Upload"; + var absoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path, $"logo.{type}"); + + // 删除已存在文件 + if (File.Exists(oldSysLogoAbsoluteFilePath)) + File.Delete(oldSysLogoAbsoluteFilePath); + + // 创建文件夹 + var absoluteFileDir = Path.GetDirectoryName(absoluteFilePath); + if (!Directory.Exists(absoluteFileDir)) + Directory.CreateDirectory(absoluteFileDir); + + // 保存图标文件 + await File.WriteAllBytesAsync(absoluteFilePath, binData); + + // 保存图标配置 + var relativeUrl = $"/{path}/logo.{type}"; + await UpdateConfigValue("sys_web_logo", relativeUrl); + } + + await UpdateConfigValue("sys_web_title", input.SysTitle); + await UpdateConfigValue("sys_web_viceTitle", input.SysViceTitle); + await UpdateConfigValue("sys_web_viceDesc", input.SysViceDesc); + await UpdateConfigValue("sys_web_watermark", input.SysWatermark); + await UpdateConfigValue("sys_web_copyright", input.SysCopyright); + await UpdateConfigValue("sys_web_icp", input.SysIcp); + await UpdateConfigValue("sys_web_icpUrl", input.SysIcpUrl); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs b/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs new file mode 100644 index 00000000..698f9577 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Const/Dto/ConstOutput.cs @@ -0,0 +1,25 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class ConstOutput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public dynamic Code { get; set; } + + /// + /// 扩展字段 + /// + public dynamic Data { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs b/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs new file mode 100644 index 00000000..ef15881c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs @@ -0,0 +1,82 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统常量服务 🧩 +/// +[ApiDescriptionSettings(Order = 280)] +[AllowAnonymous] +public class SysConstService : IDynamicApiController, ITransient +{ + private readonly SysCacheService _sysCacheService; + + public SysConstService(SysCacheService sysCacheService) + { + _sysCacheService = sysCacheService; + } + + /// + /// 获取所有常量列表 🔖 + /// + /// + [DisplayName("获取所有常量列表")] + public async Task> GetList() + { + var key = $"{CacheConst.KeyConst}list"; + var constlist = _sysCacheService.Get>(key); + if (constlist == null) + { + var typeList = GetConstAttributeList(); + constlist = typeList.Select(u => new ConstOutput + { + Name = u.CustomAttributes.ToList().FirstOrDefault()?.ConstructorArguments.ToList().FirstOrDefault().Value?.ToString() ?? u.Name, + Code = u.Name, + Data = GetData(Convert.ToString(u.Name)) + }).ToList(); + _sysCacheService.Set(key, constlist); + } + return await Task.FromResult(constlist); + } + + /// + /// 根据类名获取常量数据 🔖 + /// + /// + /// + [DisplayName("根据类名获取常量数据")] + public async Task> GetData([Required] string typeName) + { + var key = $"{CacheConst.KeyConst}{typeName.ToUpper()}"; + var constlist = _sysCacheService.Get>(key); + if (constlist == null) + { + var typeList = GetConstAttributeList(); + var type = typeList.FirstOrDefault(u => u.Name == typeName); + + var isEnum = type.BaseType.Name == "Enum"; + constlist = type.GetFields()? + .Where(isEnum, u => u.FieldType.Name == typeName) + .Select(u => new ConstOutput + { + Name = u.Name, + Code = isEnum ? (int)u.GetValue(BindingFlags.Instance) : u.GetValue(BindingFlags.Instance) + }).ToList(); + _sysCacheService.Set(key, constlist); + } + return await Task.FromResult(constlist); + } + + /// + /// 获取常量特性类型列表 + /// + /// + private List GetConstAttributeList() + { + return App.EffectiveTypes.Where(u => u.CustomAttributes.Any(c => c.AttributeType == typeof(ConstAttribute))).ToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateEntityInput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateEntityInput.cs new file mode 100644 index 00000000..ffc30454 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateEntityInput.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class CreateEntityInput +{ + /// + /// 表名 + /// + /// student + public string TableName { get; set; } + + /// + /// 实体名 + /// + /// Student + public string EntityName { get; set; } + + /// + /// 基类名 + /// + /// AutoIncrementEntity + public string BaseClassName { get; set; } + + /// + /// 导出位置 + /// + /// Web.Application + public string Position { get; set; } + + /// + /// 库标识 + /// + public string ConfigId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateSeedDataInput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateSeedDataInput.cs new file mode 100644 index 00000000..e84385d3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/CreateSeedDataInput.cs @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class CreateSeedDataInput +{ + /// + /// 库标识 + /// + public string ConfigId { get; set; } + + /// + /// 表名 + /// + /// student + public string TableName { get; set; } + + /// + /// 实体名称 + /// + /// Student + public string EntityName { get; set; } + + /// + /// 种子名称 + /// + /// Student + public string SeedDataName { get; set; } + + /// + /// 导出位置 + /// + /// Web.Application + public string Position { get; set; } + + /// + /// 后缀 + /// + /// Web.Application + public string Suffix { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs new file mode 100644 index 00000000..b3f20c7b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs @@ -0,0 +1,52 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DbColumnInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string DbColumnName { get; set; } + + public string DataType { get; set; } + + public int Length { get; set; } + + public string ColumnDescription { get; set; } + + public int IsNullable { get; set; } + + public int IsIdentity { get; set; } + + public int IsPrimarykey { get; set; } + + public int DecimalDigits { get; set; } +} + +public class UpdateDbColumnInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string ColumnName { get; set; } + + public string OldColumnName { get; set; } + + public string Description { get; set; } +} + +public class DeleteDbColumnInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string DbColumnName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnOutput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnOutput.cs new file mode 100644 index 00000000..87e30b9c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnOutput.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DbColumnOutput +{ + public string TableName { get; set; } + + public int TableId { get; set; } + + public string DbColumnName { get; set; } + + public string PropertyName { get; set; } + + public string DataType { get; set; } + + public object PropertyType { get; set; } + + public int Length { get; set; } + + public string ColumnDescription { get; set; } + + public string DefaultValue { get; set; } + + public bool IsNullable { get; set; } + + public bool IsIdentity { get; set; } + + public bool IsPrimarykey { get; set; } + + public object Value { get; set; } + + public int DecimalDigits { get; set; } + + public int Scale { get; set; } + + public bool IsArray { get; set; } + + public bool IsJson { get; set; } + + public bool? IsUnsigned { get; set; } + + public int CreateTableFieldSort { get; set; } + + internal object SqlParameterDbType { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableInput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableInput.cs new file mode 100644 index 00000000..9c64d5d6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableInput.cs @@ -0,0 +1,36 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DbTableInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string Description { get; set; } + + public List DbColumnInfoList { get; set; } +} + +public class UpdateDbTableInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string OldTableName { get; set; } + + public string Description { get; set; } +} + +public class DeleteDbTableInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableVisual.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableVisual.cs new file mode 100644 index 00000000..7349fee3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbTableVisual.cs @@ -0,0 +1,56 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 库表可视化 +/// +public class VisualDbTable +{ + public List VisualTableList { get; set; } + + public List VisualColumnList { get; set; } + + public List ColumnRelationList { get; set; } +} + +public class VisualTable +{ + public string TableName { get; set; } + + public string TableComents { get; set; } + + public int X { get; set; } + + public int Y { get; set; } +} + +public class VisualColumn +{ + public string TableName { get; set; } + + public string ColumnName { get; set; } + + public string DataType { get; set; } + + public string DataLength { get; set; } + + public string ColumnDescription { get; set; } +} + +public class ColumnRelation +{ + public string SourceTableName { get; set; } + + public string SourceColumnName { get; set; } + + public string Type { get; set; } + + public string TargetTableName { get; set; } + + public string TargetColumnName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/JsonIgnoredPropertyData.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/JsonIgnoredPropertyData.cs new file mode 100644 index 00000000..4727626a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/JsonIgnoredPropertyData.cs @@ -0,0 +1,28 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 保存标注了JsonIgnore的Property的值信息 +/// +public class JsonIgnoredPropertyData +{ + /// + /// 记录索引 + /// + public int RecordIndex { get; set; } + + /// + /// 属性名 + /// + public string Name { get; set; } + + /// + /// 属性值描述 + /// + public string Value { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs new file mode 100644 index 00000000..18a6c25f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs @@ -0,0 +1,582 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Npgsql; + +namespace Admin.NET.Core.Service; + +/// +/// 系统数据库管理服务 🧩 +/// +[ApiDescriptionSettings(Order = 250)] +public class SysDatabaseService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + private readonly IViewEngine _viewEngine; + private readonly CodeGenOptions _codeGenOptions; + + public SysDatabaseService(ISqlSugarClient db, + IViewEngine viewEngine, + IOptions codeGenOptions) + { + _db = db; + _viewEngine = viewEngine; + _codeGenOptions = codeGenOptions.Value; + } + + /// + /// 获取库列表 🔖 + /// + /// + [DisplayName("获取库列表")] + public List GetList() + { + return App.GetOptions().ConnectionConfigs.Select(u => u.ConfigId.ToString()).ToList(); + } + + /// + /// 获取可视化库表结构 🔖 + /// + /// + [DisplayName("获取可视化库表结构")] + public VisualDbTable GetVisualDbTable() + { + var visualTableList = new List(); + var visualColumnList = new List(); + var columnRelationList = new List(); + + // 遍历所有实体获取所有库表结构 + var random = new Random(); + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)).ToList(); + foreach (var entityType in entityTypes) + { + var entityInfo = _db.EntityMaintenance.GetEntityInfoNoCache(entityType); + + var visualTable = new VisualTable + { + TableName = entityInfo.DbTableName, + TableComents = entityInfo.TableDescription + entityInfo.DbTableName, + X = random.Next(5000), + Y = random.Next(5000) + }; + visualTableList.Add(visualTable); + + foreach (EntityColumnInfo columnInfo in entityInfo.Columns) + { + var visualColumn = new VisualColumn + { + TableName = columnInfo.DbTableName, + ColumnName = columnInfo.DbColumnName, + DataType = columnInfo.PropertyInfo.PropertyType.Name, + DataLength = columnInfo.Length.ToString(), + ColumnDescription = columnInfo.ColumnDescription, + }; + visualColumnList.Add(visualColumn); + + // 根据导航配置获取表之间关联关系 + if (columnInfo.Navigat != null) + { + var name1 = columnInfo.Navigat.GetName(); + var name2 = columnInfo.Navigat.GetName2(); + var relation = new ColumnRelation + { + SourceTableName = columnInfo.DbTableName, + SourceColumnName = name1, + Type = columnInfo.Navigat.GetNavigateType() == NavigateType.OneToOne ? "ONE_TO_ONE" : "ONE_TO_MANY", + TargetTableName = columnInfo.DbColumnName, + TargetColumnName = string.IsNullOrEmpty(name2) ? "Id" : name2 + }; + columnRelationList.Add(relation); + } + } + } + + return new VisualDbTable { VisualTableList = visualTableList, VisualColumnList = visualColumnList, ColumnRelationList = columnRelationList }; + } + + /// + /// 获取字段列表 🔖 + /// + /// 表名 + /// ConfigId + /// + [AllowAnonymous] + [DisplayName("获取字段列表")] + public List GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + if (string.IsNullOrWhiteSpace(tableName)) + return new List(); + + return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt>(); + } + + /// + /// 获取数据库数据类型列表 🔖 + /// + /// + /// + [DisplayName("获取数据库数据类型列表")] + public List GetDbTypeList(string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + return db.DbMaintenance.GetDbTypes().OrderBy(u => u).ToList(); + } + + /// + /// 增加列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "AddColumn"), HttpPost] + [DisplayName("增加列")] + public void AddColumn(DbColumnInput input) + { + var column = new DbColumnInfo + { + ColumnDescription = input.ColumnDescription, + DbColumnName = input.DbColumnName, + IsIdentity = input.IsIdentity == 1, + IsNullable = input.IsNullable == 1, + IsPrimarykey = input.IsPrimarykey == 1, + Length = input.Length, + DecimalDigits = input.DecimalDigits, + DataType = input.DataType + }; + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.AddColumn(input.TableName, column); + db.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription); + if (column.IsPrimarykey) + db.DbMaintenance.AddPrimaryKey(input.TableName, input.DbColumnName); + } + + /// + /// 删除列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "DeleteColumn"), HttpPost] + [DisplayName("删除列")] + public void DeleteColumn(DeleteDbColumnInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.DropColumn(input.TableName, input.DbColumnName); + } + + /// + /// 编辑列 🔖 + /// + /// + [ApiDescriptionSettings(Name = "UpdateColumn"), HttpPost] + [DisplayName("编辑列")] + public void UpdateColumn(UpdateDbColumnInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.RenameColumn(input.TableName, input.OldColumnName, input.ColumnName); + if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName)) + db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName); + db.DbMaintenance.AddColumnRemark(input.ColumnName, input.TableName, string.IsNullOrWhiteSpace(input.Description) ? input.ColumnName : input.Description); + } + + /// + /// 获取表列表 🔖 + /// + /// ConfigId + /// + [DisplayName("获取表列表")] + public List GetTableList(string configId = SqlSugarConst.MainConfigId) + { + var db = _db.AsTenant().GetConnectionScope(configId); + return db.DbMaintenance.GetTableInfoList(false); + } + + /// + /// 增加表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "AddTable"), HttpPost] + [DisplayName("增加表")] + public void AddTable(DbTableInput input) + { + if (input.DbColumnInfoList == null || !input.DbColumnInfoList.Any()) + throw Oops.Oh(ErrorCodeEnum.db1000); + + if (input.DbColumnInfoList.GroupBy(u => u.DbColumnName).Any(u => u.Count() > 1)) + throw Oops.Oh(ErrorCodeEnum.db1002); + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var typeBuilder = db.DynamicBuilder().CreateClass(input.TableName, new SugarTable() { TableName = input.TableName, TableDescription = input.Description }); + input.DbColumnInfoList.ForEach(u => + { + var dbColumnName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(u.DbColumnName.Trim()) : u.DbColumnName.Trim(); + // 虚拟类都默认string类型,具体以列数据类型为准 + typeBuilder.CreateProperty(dbColumnName, typeof(string), new SugarColumn() + { + IsPrimaryKey = u.IsPrimarykey == 1, + IsIdentity = u.IsIdentity == 1, + ColumnDataType = u.DataType, + Length = u.Length, + IsNullable = u.IsNullable == 1, + DecimalDigits = u.DecimalDigits, + ColumnDescription = u.ColumnDescription, + }); + }); + db.CodeFirst.InitTables(typeBuilder.BuilderType()); + } + + /// + /// 删除表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "DeleteTable"), HttpPost] + [DisplayName("删除表")] + public void DeleteTable(DeleteDbTableInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.DropTable(input.TableName); + } + + /// + /// 编辑表 🔖 + /// + /// + [ApiDescriptionSettings(Name = "UpdateTable"), HttpPost] + [DisplayName("编辑表")] + public void UpdateTable(UpdateDbTableInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + db.DbMaintenance.RenameTable(input.OldTableName, input.TableName); + try + { + if (db.DbMaintenance.IsAnyTableRemark(input.TableName)) + db.DbMaintenance.DeleteTableRemark(input.TableName); + else + db.DbMaintenance.AddTableRemark(input.TableName, input.Description); + } + catch (NotSupportedException ex) + { + throw Oops.Oh(ex.ToString()); + } + } + + /// + /// 创建实体 🔖 + /// + /// + [ApiDescriptionSettings(Name = "CreateEntity"), HttpPost] + [DisplayName("创建实体")] + public void CreateEntity(CreateEntityInput input) + { + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Application" : input.Position; + input.EntityName = string.IsNullOrWhiteSpace(input.EntityName) ? (config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName; + string[] dbColumnNames = Array.Empty(); + // Entity.cs.vm中是允许创建没有基类的实体的,所以这里也要做出相同的判断 + if (!string.IsNullOrWhiteSpace(input.BaseClassName)) + { + _codeGenOptions.EntityBaseColumn.TryGetValue(input.BaseClassName, out dbColumnNames); + if (dbColumnNames is null || dbColumnNames is { Length: 0 }) + throw Oops.Oh("基类配置文件不存在此类型"); + } + var templatePath = GetEntityTemplatePath(); + var targetPath = GetEntityTargetPath(input); + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + DbTableInfo dbTableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == input.TableName || u.Name == input.TableName.ToLower()) ?? throw Oops.Oh(ErrorCodeEnum.db1001); + List dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); + dbColumnInfos.ForEach(u => + { + u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 + u.DataType = CodeGenUtil.ConvertDataType(u, config.DbType); + }); + if (_codeGenOptions.BaseEntityNames.Contains(input.BaseClassName, StringComparer.OrdinalIgnoreCase)) + dbColumnInfos = dbColumnInfos.Where(u => !dbColumnNames.Contains(u.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList(); + + var tContent = File.ReadAllText(templatePath); + var tResult = _viewEngine.RunCompileFromCached(tContent, new + { + NameSpace = $"{input.Position}.Entity", + input.TableName, + input.EntityName, + BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $" : {input.BaseClassName}", + input.ConfigId, + dbTableInfo.Description, + TableField = dbColumnInfos + }); + File.WriteAllText(targetPath, tResult, Encoding.UTF8); + } + + /// + /// 创建种子数据 🔖 + /// + /// + [ApiDescriptionSettings(Name = "CreateSeedData"), HttpPost] + [DisplayName("创建种子数据")] + public async void CreateSeedData(CreateSeedDataInput input) + { + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Core" : input.Position; + + var templatePath = GetSeedDataTemplatePath(); + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var tableInfo = db.DbMaintenance.GetTableInfoList(false).First(u => u.Name == input.TableName); // 表名 + List dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); // 所有字段 + IEnumerable entityInfos = await GetEntityInfos(); + Type entityType = null; + foreach (var item in entityInfos) + { + if (tableInfo.Name.ToLower() != (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()) continue; + entityType = item.Type; + break; + } + if (entityType == null) return; + + input.EntityName = entityType.Name; + input.SeedDataName = entityType.Name + "SeedData"; + if (!string.IsNullOrWhiteSpace(input.Suffix)) + input.SeedDataName += input.Suffix; + var targetPath = GetSeedDataTargetPath(input); + + // 查询所有数据 + var query = db.QueryableByObject(entityType); + DbColumnInfo orderField = null; // 排序字段 + // 优先用创建时间排序 + orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "create_time" || u.DbColumnName.ToLower() == "createtime").FirstOrDefault(); + if (orderField != null) + query.OrderBy(orderField.DbColumnName); + // 其次用Id排序 + orderField = dbColumnInfos.Where(u => u.DbColumnName.ToLower() == "id").FirstOrDefault(); + if (orderField != null) + query.OrderBy(orderField.DbColumnName); + object records = query.ToList(); + var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; + var recordsJSON = JsonConvert.SerializeObject(records, Formatting.Indented, timeConverter); + + // 检查有没有 System.Text.Json.Serialization.JsonIgnore 的属性 + var jsonIgnoreProperties = entityType.GetProperties().Where(p => (p.GetAttribute() != null || + p.GetAttribute() != null) && p.GetAttribute() != null).ToList(); + var jsonIgnoreInfo = new List>(); + if (jsonIgnoreProperties.Count > 0) + { + int recordIndex = 0; + foreach (var r in (IEnumerable)records) + { + List record = new(); + foreach (var item in jsonIgnoreProperties) + { + object v = item.GetValue(r); + string strValue = "null"; + if (v != null) + { + strValue = v.ToString(); + if (v.GetType() == typeof(string)) + strValue = "\"" + strValue + "\""; + else if (v.GetType() == typeof(DateTime)) + strValue = "DateTime.Parse(\"" + ((DateTime)v).ToString("yyyy-MM-dd HH:mm:ss") + "\")"; + } + record.Add(new JsonIgnoredPropertyData { RecordIndex = recordIndex, Name = item.Name, Value = strValue }); + } + recordIndex++; + jsonIgnoreInfo.Add(record); + } + } + + var tContent = File.ReadAllText(templatePath); + var data = new + { + NameSpace = $"{input.Position}.SeedData", + EntityNameSpace = entityType.Namespace, + input.TableName, + input.EntityName, + input.SeedDataName, + input.ConfigId, + tableInfo.Description, + JsonIgnoreInfo = jsonIgnoreInfo, + RecordsJSON = recordsJSON + }; + var tResult = _viewEngine.RunCompile(tContent, data, builderAction: builder => + { + builder.AddAssemblyReferenceByName("System.Linq"); + builder.AddAssemblyReferenceByName("System.Collections"); + builder.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + File.WriteAllText(targetPath, tResult, Encoding.UTF8); + } + + /// + /// 获取库表信息 + /// + /// + private async Task> GetEntityInfos() + { + var entityInfos = new List(); + + var type = typeof(SugarTable); + var types = new List(); + if (_codeGenOptions.EntityAssemblyNames != null) + { + foreach (var assemblyName in _codeGenOptions.EntityAssemblyNames) + { + Assembly asm = Assembly.Load(assemblyName); + types.AddRange(asm.GetExportedTypes().ToList()); + } + } + bool IsMyAttribute(Attribute[] o) + { + foreach (Attribute a in o) + { + if (a.GetType() == type) + return true; + } + return false; + } + Type[] cosType = types.Where(o => + { + return IsMyAttribute(Attribute.GetCustomAttributes(o, true)); + } + ).ToArray(); + + foreach (var c in cosType) + { + var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault(); + + var des = c.GetCustomAttributes(typeof(DescriptionAttribute), true); + var description = ""; + if (des.Length > 0) + { + description = ((DescriptionAttribute)des[0]).Description; + } + entityInfos.Add(new EntityInfo() + { + EntityName = c.Name, + DbTableName = sugarAttribute == null ? c.Name : ((SugarTable)sugarAttribute).TableName, + TableDescription = description, + Type = c + }); + } + return await Task.FromResult(entityInfos); + } + + /// + /// 获取实体模板文件路径 + /// + /// + private static string GetEntityTemplatePath() + { + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + return Path.Combine(templatePath, "Entity.cs.vm"); + } + + /// + /// 获取种子数据模板文件路径 + /// + /// + private static string GetSeedDataTemplatePath() + { + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + return Path.Combine(templatePath, "SeedData.cs.vm"); + } + + /// + /// 设置生成实体文件路径 + /// + /// + /// + private static string GetEntityTargetPath(CreateEntityInput input) + { + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "Entity"); + if (!Directory.Exists(backendPath)) + Directory.CreateDirectory(backendPath); + return Path.Combine(backendPath, input.EntityName + ".cs"); + } + + /// + /// 设置生成种子数据文件路径 + /// + /// + /// + private static string GetSeedDataTargetPath(CreateSeedDataInput input) + { + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "SeedData"); + if (!Directory.Exists(backendPath)) + Directory.CreateDirectory(backendPath); + return Path.Combine(backendPath, input.SeedDataName + ".cs"); + } + + /// + /// 备份数据库(PostgreSQL)🔖 + /// + /// + [HttpPost, NonUnify] + [DisplayName("备份数据库(PostgreSQL)")] + public async Task BackupDatabase() + { + if (_db.CurrentConnectionConfig.DbType != SqlSugar.DbType.PostgreSQL) + throw Oops.Oh("只支持 PostgreSQL 数据库 😁"); + + var npgsqlConn = new NpgsqlConnectionStringBuilder(_db.CurrentConnectionConfig.ConnectionString); + if (npgsqlConn == null || string.IsNullOrWhiteSpace(npgsqlConn.Host) || string.IsNullOrWhiteSpace(npgsqlConn.Username) || string.IsNullOrWhiteSpace(npgsqlConn.Password) || string.IsNullOrWhiteSpace(npgsqlConn.Database)) + throw Oops.Oh("PostgreSQL 数据库配置错误"); + + // 确保备份目录存在 + var backupDirectory = Path.Combine(Directory.GetCurrentDirectory(), "backups"); + Directory.CreateDirectory(backupDirectory); + + // 构建备份文件名 + string backupFileName = $"backup_{DateTime.Now:yyyyMMddHHmmss}.sql"; + string backupFilePath = Path.Combine(backupDirectory, backupFileName); + + // 启动pg_dump进程进行备份 + // 设置密码:export PGPASSWORD='xxxxxx' + var bash = $"-U {npgsqlConn.Username} -h {npgsqlConn.Host} -p {npgsqlConn.Port} -E UTF8 -F c -b -v -f {backupFilePath} {npgsqlConn.Database}"; + var startInfo = new ProcessStartInfo + { + FileName = "pg_dump", + Arguments = bash, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + EnvironmentVariables = + { + ["PGPASSWORD"] = npgsqlConn.Password + } + }; + + //_logger.LogInformation("备份数据库:pg_dump " + bash); + + //try + //{ + using (var backupProcess = Process.Start(startInfo)) + { + await backupProcess.WaitForExitAsync(); + + //var output = await backupProcess.StandardOutput.ReadToEndAsync(); + //var error = await backupProcess.StandardError.ReadToEndAsync(); + + // 检查备份是否成功 + if (backupProcess.ExitCode != 0) + { + throw Oops.Oh($"备份失败:ExitCode({backupProcess.ExitCode})"); + } + } + + // _logger.LogInformation($"备份成功:{backupFilePath}"); + //} + //catch (Exception ex) + //{ + // _logger.LogError(ex, $"备份失败:"); + // throw; + //} + + // 若备份成功则提供下载链接 + return new FileStreamResult(new FileStream(backupFilePath, FileMode.Open), "application/octet-stream") + { + FileDownloadName = backupFileName + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs new file mode 100644 index 00000000..eccdff36 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DictDataInput : BaseIdInput +{ + /// + /// 状态 + /// + public StatusEnum Status { get; set; } +} + +public class PageDictDataInput : BasePageInput +{ + /// + /// 字典类型Id + /// + public long DictTypeId { get; set; } + + /// + /// 值 + /// + public string Value { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class AddDictDataInput : SysDictData +{ +} + +public class UpdateDictDataInput : AddDictDataInput +{ +} + +public class DeleteDictDataInput : BaseIdInput +{ +} + +public class GetDataDictDataInput +{ + /// + /// 字典类型Id + /// + [Required(ErrorMessage = "字典类型Id不能为空"), DataValidation(ValidationTypes.Numeric)] + public long DictTypeId { get; set; } +} + +public class QueryDictDataInput +{ + /// + /// 编码 + /// + [Required(ErrorMessage = "字典唯一编码不能为空")] + public string Code { get; set; } + + /// + /// 状态 + /// + public int? Status { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs new file mode 100644 index 00000000..7884ada2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs @@ -0,0 +1,49 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class DictTypeInput : BaseIdInput +{ + /// + /// 状态 + /// + public StatusEnum Status { get; set; } +} + +public class PageDictTypeInput : BasePageInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class AddDictTypeInput : SysDictType +{ +} + +public class UpdateDictTypeInput : AddDictTypeInput +{ +} + +public class DeleteDictTypeInput : BaseIdInput +{ +} + +public class GetDataDictTypeInput +{ + /// + /// 编码 + /// + [Required(ErrorMessage = "字典类型编码不能为空")] + public string Code { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs new file mode 100644 index 00000000..0245fb0f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs @@ -0,0 +1,184 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统字典值服务 🧩 +/// +[ApiDescriptionSettings(Order = 420)] +[AllowAnonymous] +public class SysDictDataService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysDictDataRep; + + public SysDictDataService(SqlSugarRepository sysDictDataRep) + { + _sysDictDataRep = sysDictDataRep; + } + + /// + /// 获取字典值分页列表 🔖 + /// + /// + /// + [DisplayName("获取字典值分页列表")] + public async Task> Page(PageDictDataInput input) + { + return await _sysDictDataRep.AsQueryable() + .Where(u => u.DictTypeId == input.DictTypeId) + .WhereIF(!string.IsNullOrEmpty(input.Code?.Trim()), u => u.Code.Contains(input.Code)) + .WhereIF(!string.IsNullOrEmpty(input.Value?.Trim()), u => u.Value.Contains(input.Value)) + .OrderBy(u => new { u.OrderNo, u.Code }) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取字典值列表 🔖 + /// + /// + [DisplayName("获取字典值列表")] + public async Task> GetList([FromQuery] GetDataDictDataInput input) + { + return await GetDictDataListByDictTypeId(input.DictTypeId); + } + + /// + /// 增加字典值 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加字典值")] + public async Task AddDictData(AddDictDataInput input) + { + var isExist = await _sysDictDataRep.IsAnyAsync(u => u.Code == input.Code && u.DictTypeId == input.DictTypeId); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D3003); + + await _sysDictDataRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新字典值 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新字典值")] + public async Task UpdateDictData(UpdateDictDataInput input) + { + var isExist = await _sysDictDataRep.IsAnyAsync(u => u.Id == input.Id); + if (!isExist) throw Oops.Oh(ErrorCodeEnum.D3004); + + isExist = await _sysDictDataRep.IsAnyAsync(u => u.Code == input.Code && u.DictTypeId == input.DictTypeId && u.Id != input.Id); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3003); + + await _sysDictDataRep.UpdateAsync(input.Adapt()); + } + + /// + /// 删除字典值 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除字典值")] + public async Task DeleteDictData(DeleteDictDataInput input) + { + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); + if (dictData == null) + throw Oops.Oh(ErrorCodeEnum.D3004); + + await _sysDictDataRep.DeleteAsync(dictData); + } + + /// + /// 获取字典值详情 🔖 + /// + /// + /// + [DisplayName("获取字典值详情")] + public async Task GetDetail([FromQuery] DictDataInput input) + { + return await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); + } + + /// + /// 修改字典值状态 🔖 + /// + /// + /// + [DisplayName("修改字典值状态")] + public async Task SetStatus(DictDataInput input) + { + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); + if (dictData == null) + throw Oops.Oh(ErrorCodeEnum.D3004); + + if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) + throw Oops.Oh(ErrorCodeEnum.D3005); + + dictData.Status = input.Status; + await _sysDictDataRep.UpdateAsync(dictData); + } + + /// + /// 根据字典类型Id获取字典值集合 + /// + /// + /// + [NonAction] + public async Task> GetDictDataListByDictTypeId(long dictTypeId) + { + return await _sysDictDataRep.AsQueryable() + .Where(u => u.DictTypeId == dictTypeId) + .OrderBy(u => new { u.OrderNo, u.Code }) + .ToListAsync(); + } + + /// + /// 根据字典类型编码获取字典值集合 🔖 + /// + /// + /// + [DisplayName("根据字典类型编码获取字典值集合")] + public async Task> GetDataList(string code) + { + return await _sysDictDataRep.Context.Queryable() + .LeftJoin((u, a) => u.Id == a.DictTypeId) + .Where((u, a) => u.Code == code && u.Status == StatusEnum.Enable && a.Status == StatusEnum.Enable) + .OrderBy((u, a) => new { a.OrderNo, a.Code }) + .Select((u, a) => a).ToListAsync(); + } + + /// + /// 根据查询条件获取字典值集合 🔖 + /// + /// + /// + [DisplayName("根据查询条件获取字典值集合")] + public async Task> GetDataList([FromQuery] QueryDictDataInput input) + { + return await _sysDictDataRep.Context.Queryable() + .LeftJoin((u, a) => u.Id == a.DictTypeId) + .Where((u, a) => u.Code == input.Code) + .WhereIF(input.Status.HasValue, (u, a) => a.Status == (StatusEnum)input.Status.Value) + .OrderBy((u, a) => new { a.OrderNo, a.Code }) + .Select((u, a) => a).ToListAsync(); + } + + /// + /// 根据字典类型Id删除字典值 + /// + /// + /// + [NonAction] + public async Task DeleteDictData(long dictTypeId) + { + await _sysDictDataRep.DeleteAsync(u => u.DictTypeId == dictTypeId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs new file mode 100644 index 00000000..e68dbc5d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs @@ -0,0 +1,165 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统字典类型服务 🧩 +/// +[ApiDescriptionSettings(Order = 430)] +[AllowAnonymous] +public class SysDictTypeService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysDictTypeRep; + private readonly SysDictDataService _sysDictDataService; + + public SysDictTypeService(SqlSugarRepository sysDictTypeRep, + SysDictDataService sysDictDataService) + { + _sysDictTypeRep = sysDictTypeRep; + _sysDictDataService = sysDictDataService; + } + + /// + /// 获取字典类型分页列表 🔖 + /// + /// + [DisplayName("获取字典类型分页列表")] + public async Task> Page(PageDictTypeInput input) + { + return await _sysDictTypeRep.AsQueryable() + .WhereIF(!string.IsNullOrEmpty(input.Code?.Trim()), u => u.Code.Contains(input.Code)) + .WhereIF(!string.IsNullOrEmpty(input.Name?.Trim()), u => u.Name.Contains(input.Name)) + .OrderBy(u => new { u.OrderNo, u.Code }) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取字典类型列表 🔖 + /// + /// + [DisplayName("获取字典类型列表")] + public async Task> GetList() + { + return await _sysDictTypeRep.AsQueryable().OrderBy(u => new { u.OrderNo, u.Code }).ToListAsync(); + } + + /// + /// 获取字典类型-值列表 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("获取字典类型-值列表")] + public async Task> GetDataList([FromQuery] GetDataDictTypeInput input) + { + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code); + if (dictType == null) + throw Oops.Oh(ErrorCodeEnum.D3000); + + return await _sysDictDataService.GetDictDataListByDictTypeId(dictType.Id); + } + + /// + /// 添加字典类型 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("添加字典类型")] + public async Task AddDictType(AddDictTypeInput input) + { + var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D3001); + + await _sysDictTypeRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新字典类型 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新字典类型")] + public async Task UpdateDictType(UpdateDictTypeInput input) + { + var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Id == input.Id); + if (!isExist) + throw Oops.Oh(ErrorCodeEnum.D3000); + + isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D3001); + + await _sysDictTypeRep.UpdateAsync(input.Adapt()); + } + + /// + /// 删除字典类型 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除字典类型")] + public async Task DeleteDictType(DeleteDictTypeInput input) + { + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); + if (dictType == null) + throw Oops.Oh(ErrorCodeEnum.D3000); + + // 删除字典值 + await _sysDictTypeRep.DeleteAsync(dictType); + await _sysDictDataService.DeleteDictData(input.Id); + } + + /// + /// 获取字典类型详情 🔖 + /// + /// + /// + [DisplayName("获取字典类型详情")] + public async Task GetDetail([FromQuery] DictTypeInput input) + { + return await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); + } + + /// + /// 修改字典类型状态 🔖 + /// + /// + /// + [DisplayName("修改字典类型状态")] + public async Task SetStatus(DictTypeInput input) + { + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); + if (dictType == null) + throw Oops.Oh(ErrorCodeEnum.D3000); + + if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) + throw Oops.Oh(ErrorCodeEnum.D3005); + + dictType.Status = input.Status; + await _sysDictTypeRep.UpdateAsync(dictType); + } + + /// + /// 获取所有字典集合 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("获取所有字典集合")] + public async Task GetAllDictList() + { + var ds = await _sysDictTypeRep.AsQueryable() + .InnerJoin((u, a) => u.Id == a.DictTypeId) + .Where((u, a) => u.IsDelete == false && a.IsDelete == false && a.Status == StatusEnum.Enable) + .Select((u, a) => new { TypeCode = u.Code, a.Code, a.Name, a.Value, a.Remark, a.OrderNo, a.TagType }) + .ToListAsync(); + return ds.OrderBy(u => u.OrderNo).GroupBy(u => u.TypeCode).ToDictionary(u => u.Key, u => u); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumInput.cs b/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumInput.cs new file mode 100644 index 00000000..64506bb5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumInput.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 枚举输入参数 +/// +public class EnumInput +{ + /// + /// 枚举类型名称 + /// + /// AccountTypeEnum + [Required(ErrorMessage = "枚举类型不能为空")] + public string EnumName { get; set; } +} + +public class QueryEnumDataInput +{ + /// + /// 实体名称 + /// + /// SysUser + [Required(ErrorMessage = "实体名称不能为空")] + public string EntityName { get; set; } + + /// + /// 字段名称 + /// + /// AccountType + [Required(ErrorMessage = "字段名称不能为空")] + public string FieldName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumOutput.cs b/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumOutput.cs new file mode 100644 index 00000000..f7b90066 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Enum/Dto/EnumOutput.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 枚举类型输出参数 +/// +public class EnumTypeOutput +{ + /// + /// 枚举类型描述 + /// + public string TypeDescribe { get; set; } + + /// + /// 枚举类型名称 + /// + public string TypeName { get; set; } + + /// + /// 枚举类型备注 + /// + public string TypeRemark { get; set; } + + /// + /// 枚举实体 + /// + public List EnumEntities { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs new file mode 100644 index 00000000..0a1481a3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs @@ -0,0 +1,97 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统枚举服务 🧩 +/// +[ApiDescriptionSettings(Order = 275)] +[AllowAnonymous] +public class SysEnumService : IDynamicApiController, ITransient +{ + private readonly EnumOptions _enumOptions; + + public SysEnumService(IOptions enumOptions) + { + _enumOptions = enumOptions.Value; + } + + /// + /// 获取所有枚举类型 🔖 + /// + /// + [DisplayName("获取所有枚举类型")] + public List GetEnumTypeList() + { + var enumTypeList = App.EffectiveTypes.Where(u => u.IsEnum && _enumOptions.EntityAssemblyNames.Contains(u.Assembly.GetName().Name)).OrderBy(u => u.Name).OrderBy(u => u.FullName).ToList(); + + var result = new List(); + foreach (var item in enumTypeList) + { + result.Add(GetEnumDescription(item)); + } + return result; + } + + /// + /// 获取字典描述 + /// + /// + /// + private static EnumTypeOutput GetEnumDescription(Type type) + { + string description = type.Name; + var attrs = type.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attrs.Any()) + { + var att = ((DescriptionAttribute[])attrs)[0]; + description = att.Description; + } + var enumType = App.EffectiveTypes.FirstOrDefault(t => t.IsEnum && t.Name == type.Name); + return new EnumTypeOutput + { + TypeDescribe = description, + TypeName = type.Name, + TypeRemark = description, + EnumEntities = enumType.EnumToList() + }; + } + + /// + /// 通过枚举类型获取枚举值集合 🔖 + /// + /// + /// + [DisplayName("通过枚举类型获取枚举值集合")] + public List GetEnumDataList([FromQuery] EnumInput input) + { + var enumType = App.EffectiveTypes.FirstOrDefault(u => u.IsEnum && u.Name == input.EnumName); + if (enumType is not { IsEnum: true }) + throw Oops.Oh(ErrorCodeEnum.D1503); + + return enumType.EnumToList(); + } + + /// + /// 通过实体的字段名获取相关枚举值集合(目前仅支持枚举类型) 🔖 + /// + /// + /// + [DisplayName("通过实体的字段名获取相关枚举值集合")] + public static List GetEnumDataListByField([FromQuery] QueryEnumDataInput input) + { + // 获取实体类型属性 + Type entityType = App.EffectiveTypes.FirstOrDefault(u => u.Name == input.EntityName) ?? throw Oops.Oh(ErrorCodeEnum.D1504); + + // 获取字段类型 + var fieldType = entityType.GetProperties().FirstOrDefault(u => u.Name == input.FieldName)?.PropertyType; + if (fieldType is not { IsEnum: true }) + throw Oops.Oh(ErrorCodeEnum.D1503); + + return fieldType.EnumToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs new file mode 100644 index 00000000..04e10193 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs @@ -0,0 +1,195 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class FileInput : BaseIdInput +{ + /// + /// 文件名称 + /// + public string FileName { get; set; } + + /// + /// 文件类型 + /// + public string FileType { get; set; } + + /// + /// 文件Url + /// + public string? Url { get; set; } +} + +public class PageFileInput : BasePageInput +{ + /// + /// 文件名称 + /// + public string FileName { get; set; } + + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } +} + +public class DeleteFileInput : BaseIdInput +{ +} + +public class UploadFileFromBase64Input +{ + /// + /// 文件内容 + /// + public string FileDataBase64 { get; set; } + + /// + /// 文件类型( "image/jpeg",) + /// + public string ContentType { get; set; } + + /// + /// 文件名称 + /// + public string FileName { get; set; } + + /// + /// 保存路径 + /// + public string Path { get; set; } + + /// + /// 文件类型 + /// + public string FileType { get; set; } +} + +/// +/// 上传文件 +/// +public class FileUploadInput +{ + /// + /// 文件 + /// + [Required] + public IFormFile File { get; set; } + + /// + /// 文件类型 + /// + public string FileType { get; set; } + + /// + /// 文件路径 + /// + public string Path { get; set; } +} + +/// +/// 查询关联查询输入 +/// +public class RelationQueryInput +{ + /// + /// 关联对象名称 + /// + public string RelationName { get; set; } + + /// + /// 关联对象Id + /// + public long? RelationId { get; set; } + + /// + /// 文件,多个以","分割 + /// + public string FileTypes { get; set; } + + /// + /// 所属Id + /// + public long? BelongId { get; set; } + + /// + /// + /// + /// + public string[] GetFileTypeBS() + { + return FileTypes.Split(','); + } +} + +public class FileOutput +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// URL + /// + public string Url { get; set; } + + /// + /// 大小 + /// + public long SizeKb { get; set; } + + /// + /// 后缀 + /// + public string Suffix { get; set; } + + /// + /// 路径 + /// + public string FilePath { get; set; } + + /// + /// 文件类别 + /// + public string FileType { get; set; } + + /// + /// 上传人 + /// + public string CreateUserName { get; set; } + + /// + /// 上传时间 + /// + public DateTime? CreateTime { get; set; } + + /// + /// 关联对象名称 + /// + public string RelationName { get; set; } + + /// + /// 关联对象Id + /// + public long? RelationId { get; set; } + + /// + /// 所属Id + /// + public long? BelongId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs new file mode 100644 index 00000000..4d478ea7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs @@ -0,0 +1,533 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Aliyun.OSS.Util; +using Furion.VirtualFileServer; +using OnceMi.AspNetCore.OSS; + +namespace Admin.NET.Core.Service; + +/// +/// 系统文件服务 🧩 +/// +[ApiDescriptionSettings(Order = 410)] +public class SysFileService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysFileRep; + private readonly OSSProviderOptions _OSSProviderOptions; + private readonly UploadOptions _uploadOptions; + private readonly IOSSService _OSSService; + private readonly string _imageType = ".jpg.png.bmp.gif.tif"; + + public SysFileService(UserManager userManager, + SqlSugarRepository sysFileRep, + IOptions oSSProviderOptions, + IOptions uploadOptions, + IOSSServiceFactory ossServiceFactory) + { + _userManager = userManager; + _sysFileRep = sysFileRep; + _OSSProviderOptions = oSSProviderOptions.Value; + _uploadOptions = uploadOptions.Value; + if (_OSSProviderOptions.IsEnable) + _OSSService = ossServiceFactory.Create(Enum.GetName(_OSSProviderOptions.Provider)); + } + + /// + /// 获取文件分页列表 🔖 + /// + /// + /// + [DisplayName("获取文件分页列表")] + public async Task> Page(PageFileInput input) + { + return await _sysFileRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.FileName), u => u.FileName.Contains(input.FileName.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()), + u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 上传文件 🔖 + /// + /// + /// + [DisplayName("上传文件")] + public async Task UploadFile([FromForm] FileUploadInput input) + { + return await HandleUploadFile(input.File, input.Path, fileType: input.FileType); + } + + /// + /// 上传文件Base64 + /// + /// + /// + /// + /// + /// + /// + private async Task UploadFileFromBase64(string strBase64, string fileName, string contentType, string? path, string? fileType) + { + byte[] fileData = Convert.FromBase64String(strBase64); + var ms = new MemoryStream(); + ms.Write(fileData); + ms.Seek(0, SeekOrigin.Begin); + if (string.IsNullOrEmpty(fileName)) + fileName = $"{YitIdHelper.NextId()}.jpg"; + if (string.IsNullOrEmpty(contentType)) + contentType = "image/jpg"; + IFormFile formFile = new FormFile(ms, 0, fileData.Length, "file", fileName) + { + Headers = new HeaderDictionary(), + ContentType = contentType + }; + return await UploadFile(new FileUploadInput { File = formFile, Path = path, FileType = fileType }); + } + + /// + /// 上传文件Base64 🔖 + /// + /// + /// + [DisplayName("上传文件Base64")] + public async Task UploadFileFromBase64(UploadFileFromBase64Input input) + { + return await UploadFileFromBase64(input.FileDataBase64, input.FileName, input.ContentType, input.Path, input.FileType); + } + + /// + /// 上传多文件 🔖 + /// + /// + /// + [DisplayName("上传多文件")] + public async Task> UploadFiles([Required] List files) + { + var filelist = new List(); + foreach (var file in files) + { + filelist.Add(await UploadFile(new FileUploadInput { File = file })); + } + return filelist; + } + + /// + /// 根据文件Id或Url下载 🔖 + /// + /// + /// + [DisplayName("根据文件Id或Url下载")] + public async Task DownloadFile(FileInput input) + { + var file = input.Id > 0 ? await GetFile(input) : await _sysFileRep.GetFirstAsync(u => u.Url == input.Url); + var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); + + if (_OSSProviderOptions.IsEnable) + { + var filePath = string.Concat(file.FilePath, "/", file.Id.ToString() + file.Suffix); + var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); + return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean()) + { + var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return new FileStreamResult(helper.OpenRead(fullPath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + } + else + { + var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); + var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); + return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + } + + /// + /// 文件预览 + /// + /// + /// + [AllowAnonymous] + public async Task GetPreview([FromRoute] long Id) + { + var file = await GetFile(new FileInput { Id = Id }); + //var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); + + if (_OSSProviderOptions.IsEnable) + { + var filePath = string.Concat(file.FilePath, "/", file.Id.ToString() + file.Suffix); + var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); + return new FileStreamResult(stream.Stream, "application/octet-stream"); + } + else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean()) + { + var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return new FileStreamResult(helper.OpenRead(fullPath), "application/octet-stream"); + } + } + else + { + var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); + var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); + return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream"); + } + } + + /// + /// 下载指定文件Base64格式 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("下载指定文件Base64格式")] + public async Task DownloadFileBase64([FromBody] string url) + { + if (_OSSProviderOptions.IsEnable) + { + using var httpClient = new HttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (response.IsSuccessStatusCode) + { + // 读取文件内容并将其转换为 Base64 字符串 + byte[] fileBytes = await response.Content.ReadAsByteArrayAsync(); + return Convert.ToBase64String(fileBytes); + } + else + { + throw new HttpRequestException($"Request failed with status code: {response.StatusCode}"); + } + } + else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean()) + { + var sysFile = await _sysFileRep.GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return Convert.ToBase64String(helper.ReadAllBytes(sysFile.FilePath)); + } + } + else + { + var sysFile = await _sysFileRep.GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, sysFile.FilePath); + if (!Directory.Exists(filePath)) + Directory.CreateDirectory(filePath); + + var realFile = Path.Combine(filePath, $"{sysFile.Id}{sysFile.Suffix}"); + if (!File.Exists(realFile)) + throw Oops.Oh($"文件[{realFile}]不在存"); + byte[] fileBytes = File.ReadAllBytes(realFile); + return Convert.ToBase64String(fileBytes); + } + } + + /// + /// 删除文件 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除文件")] + public async Task DeleteFile(DeleteFileInput input) + { + var file = await _sysFileRep.GetFirstAsync(u => u.Id == input.Id); + if (file != null) + { + await _sysFileRep.DeleteAsync(file); + + if (_OSSProviderOptions.IsEnable) + { + await _OSSService.RemoveObjectAsync(file.BucketName.ToString(), string.Concat(file.FilePath, "/", $"{input.Id}{file.Suffix}")); + } + else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean()) + { + var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + helper.DeleteFile(fullPath); + } + } + else + { + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, file.FilePath, input.Id.ToString() + file.Suffix); + if (File.Exists(filePath)) + File.Delete(filePath); + } + } + } + + /// + /// 更新文件 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新文件")] + public async Task UpdateFile(FileInput input) + { + var isExist = await _sysFileRep.IsAnyAsync(u => u.Id == input.Id); + if (!isExist) throw Oops.Oh(ErrorCodeEnum.D8000); + + await _sysFileRep.UpdateAsync(u => new SysFile() { FileName = input.FileName, FileType = input.FileType }, u => u.Id == input.Id); + } + + /// + /// 获取文件 + /// + /// + /// + private async Task GetFile([FromQuery] FileInput input) + { + var file = await _sysFileRep.GetFirstAsync(u => u.Id == input.Id); + return file ?? throw Oops.Oh(ErrorCodeEnum.D8000); + } + + /// + /// 上传文件 + /// + /// 文件 + /// 路径 + /// 允许格式:.jpg.png.gif.tif.bmp + /// 类型 + /// + private async Task HandleUploadFile(IFormFile file, string savePath, string allowSuffix = "", string fileType = "") + { + if (file == null) throw Oops.Oh(ErrorCodeEnum.D8000); + + // 判断是否重复上传的文件 + var sizeKb = (long)(file.Length / 1024.0); // 大小KB + var fileMd5 = string.Empty; + if (_uploadOptions.EnableMd5) + { + using (var fileStream = file.OpenReadStream()) + { + fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length); + } + /* + * Mysql8 中如果使用了 utf8mb4_general_ci 之外的编码会出错,尽量避免在条件里使用.ToString() + * 因为 Squsugar 并不是把变量转换为字符串来构造SQL语句,而是构造了CAST(123 AS CHAR)这样的语句,这样这个返回值是utf8mb4_general_ci,所以容易出错。 + */ + var sysFile = await _sysFileRep.GetFirstAsync(u => u.FileMd5 == fileMd5 && u.SizeKb == sizeKb); + if (sysFile != null) return sysFile; + } + + // 验证文件类型 + if (!_uploadOptions.ContentType.Contains(file.ContentType)) + throw Oops.Oh(ErrorCodeEnum.D8001); + + // 验证文件大小 + if (sizeKb > _uploadOptions.MaxSize) + throw Oops.Oh(ErrorCodeEnum.D8002); + + // 获取文件后缀 + var suffix = Path.GetExtension(file.FileName).ToLower(); // 后缀 + if (string.IsNullOrWhiteSpace(suffix)) + { + var contentTypeProvider = FS.GetFileExtensionContentTypeProvider(); + suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key; + // 修改 image/jpeg 类型返回的 .jpeg、jpe 后缀 + if (suffix == ".jpeg" || suffix == ".jpe") + suffix = ".jpg"; + } + if (string.IsNullOrWhiteSpace(suffix)) + throw Oops.Oh(ErrorCodeEnum.D8003); + + // 防止客户端伪造文件类型 + if (!string.IsNullOrWhiteSpace(allowSuffix) && !allowSuffix.Contains(suffix)) + throw Oops.Oh(ErrorCodeEnum.D8003); + if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix)) + throw Oops.Oh(ErrorCodeEnum.D8001); + + var path = string.IsNullOrWhiteSpace(savePath) ? _uploadOptions.Path : savePath; + path = path.ParseToDateTimeForRep(); + var newFile = new SysFile + { + Id = YitIdHelper.NextId(), + // BucketName = _OSSProviderOptions.IsEnable ? _OSSProviderOptions.Provider.ToString() : "Local", + // 阿里云对bucket名称有要求,1.只能包括小写字母,数字,短横线(-)2.必须以小写字母或者数字开头 3.长度必须在3-63字节之间 + // 无法使用Provider + BucketName = _OSSProviderOptions.IsEnable ? _OSSProviderOptions.Bucket : "Local", + FileName = Path.GetFileNameWithoutExtension(file.FileName), + Suffix = suffix, + SizeKb = sizeKb, + FilePath = path, + FileMd5 = fileMd5, + FileType = fileType + }; + + var finalName = newFile.Id + suffix; // 文件最终名称 + if (_OSSProviderOptions.IsEnable) + { + newFile.Provider = Enum.GetName(_OSSProviderOptions.Provider); + var filePath = string.Concat(path, "/", finalName); + await _OSSService.PutObjectAsync(newFile.BucketName, filePath, file.OpenReadStream()); + // http://<你的bucket名字>.oss.aliyuncs.com/<你的object名字> + // 生成外链地址 方便前端预览 + switch (_OSSProviderOptions.Provider) + { + case OSSProvider.Aliyun: + newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}.{_OSSProviderOptions.Endpoint}/{filePath}"; + break; + + case OSSProvider.Minio: + // 获取Minio文件的下载或者预览地址 + // newFile.Url = await GetMinioPreviewFileUrl(newFile.BucketName, filePath);// 这种方法生成的Url是有7天有效期的,不能这样使用 + // 需要在MinIO中的Buckets开通对 Anonymous 的readonly权限 + newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{_OSSProviderOptions.Endpoint}/{newFile.BucketName}/{filePath}"; + break; + } + } + else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean()) + { + var fullPath = string.Concat(path.StartsWith("/") ? path : "/" + path, "/", finalName); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + helper.UploadFile(file.OpenReadStream(), fullPath); + } + } + else + { + newFile.Provider = ""; // 本地存储 Provider 显示为空 + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path); + if (!Directory.Exists(filePath)) + Directory.CreateDirectory(filePath); + + var realFile = Path.Combine(filePath, finalName); + using (var stream = File.Create(realFile)) + { + await file.CopyToAsync(stream); + } + + // 生成外链 + var host = CommonUtil.GetLocalhost(); + if (!host.EndsWith('/')) + host += "/"; + newFile.Url = $"{host}{newFile.FilePath}/{newFile.Id + newFile.Suffix}"; + } + await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync(); + return newFile; + } + + ///// + ///// 获取Minio文件的下载或者预览地址 + ///// + ///// 桶名 + ///// 文件名 + ///// + //private async Task GetMinioPreviewFileUrl(string bucketName, string fileName) + //{ + // return await _OSSService.PresignedGetObjectAsync(bucketName, fileName, 7); + //} + + /// + /// 上传头像 🔖 + /// + /// + /// + [DisplayName("上传头像")] + public async Task UploadAvatar([Required] IFormFile file) + { + var sysFile = await HandleUploadFile(file, "Upload/Avatar", _imageType); + + var sysUserRep = _sysFileRep.ChangeRepository>(); + var user = sysUserRep.GetFirst(u => u.Id == _userManager.UserId); + // 删除已有头像文件 + if (!string.IsNullOrWhiteSpace(user.Avatar)) + { + var fileId = Path.GetFileNameWithoutExtension(user.Avatar); + await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); + } + await sysUserRep.UpdateAsync(u => new SysUser() { Avatar = sysFile.Url }, u => u.Id == user.Id); + return sysFile; + } + + /// + /// 上传电子签名 🔖 + /// + /// + /// + [DisplayName("上传电子签名")] + public async Task UploadSignature([Required] IFormFile file) + { + var sysFile = await HandleUploadFile(file, "Upload/Signature", _imageType); + + var sysUserRep = _sysFileRep.ChangeRepository>(); + var user = sysUserRep.GetFirst(u => u.Id == _userManager.UserId); + // 删除已有电子签名文件 + if (!string.IsNullOrWhiteSpace(user.Signature) && user.Signature.EndsWith(".png")) + { + var fileId = Path.GetFileNameWithoutExtension(user.Signature); + await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); + } + await sysUserRep.UpdateAsync(u => new SysUser() { Signature = sysFile.Url }, u => u.Id == user.Id); + return sysFile; + } + + /// + /// 修改附件关联对象 + /// + /// + /// + /// + /// + /// + [NonAction] + public async Task UpdateRelation(List ids, string relationName, long relationId, long belongId = 0) + { + if (ids == null || ids.Count == 0) + return 0; + return await _sysFileRep.AsUpdateable() + .SetColumns(m => m.RelationName == relationName) + .SetColumns(m => m.RelationId == relationId) + .SetColumns(m => m.BelongId == belongId) + .Where(m => ids.Contains(m.Id)) + .ExecuteCommandAsync(); + } + + /// + /// 根据关联查询附件 + /// + /// + /// + /// + public async Task> GetRelationFiles([FromQuery] RelationQueryInput input) + { + return await _sysFileRep.AsQueryable() + .Where(m => !m.IsDelete) + .WhereIF(input.RelationId.HasValue && input.RelationId > 0, m => m.RelationId == input.RelationId) + .WhereIF(input.BelongId.HasValue && input.BelongId > 0, m => m.BelongId == input.BelongId.Value) + .WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), m => m.RelationName == input.RelationName) + .WhereIF(!string.IsNullOrWhiteSpace(input.FileTypes), m => input.GetFileTypeBS().Contains(m.FileType)) + .Select(m => new FileOutput + { + Id = m.Id, + FileType = m.FileType, + Name = m.FileName, + RelationId = m.RelationId, + BelongId = m.BelongId, + FilePath = m.FilePath, + SizeKb = m.SizeKb, + Suffix = m.Suffix, + RelationName = m.RelationName, + Url = SqlFunc.MergeString("/api/sysFile/Preview/", m.Id.ToString()), + CreateUserName = m.CreateUserName, + CreateTime = m.CreateTime, + }) + .ToListAsync(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs new file mode 100644 index 00000000..83fdd7f9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs @@ -0,0 +1,197 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 作业持久化(数据库) +/// +public class DbJobPersistence : IJobPersistence +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + + public DbJobPersistence(IServiceScopeFactory serviceScopeFactory) + { + _serviceScopeFactory = serviceScopeFactory; + } + + /// + /// 作业调度服务启动时 + /// + /// + /// + /// + public async Task> PreloadAsync(CancellationToken stoppingToken) + { + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var dynamicJobCompiler = scope.ServiceProvider.GetRequiredService(); + + // 获取所有定义的作业 + var allJobs = App.EffectiveTypes.ScanToBuilders().ToList(); + // 若数据库不存在任何作业,则直接返回 + if (!db.Queryable().Any(u => true)) return allJobs; + + // 遍历所有定义的作业 + foreach (var schedulerBuilder in allJobs) + { + // 获取作业信息构建器 + var jobBuilder = schedulerBuilder.GetJobBuilder(); + + // 加载数据库数据 + var dbDetail = await db.Queryable().FirstAsync(u => u.JobId == jobBuilder.JobId); + if (dbDetail == null) continue; + + // 同步数据库数据 + jobBuilder.LoadFrom(dbDetail); + + // 获取作业的所有数据库的触发器 + var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(); + // 遍历所有作业触发器 + foreach (var (_, triggerBuilder) in schedulerBuilder.GetEnumerable()) + { + // 加载数据库数据 + var dbTrigger = dbTriggers.FirstOrDefault(u => u.JobId == jobBuilder.JobId && u.TriggerId == triggerBuilder.TriggerId); + if (dbTrigger == null) continue; + + triggerBuilder.LoadFrom(dbTrigger).Updated(); // 标记更新 + } + // 遍历所有非编译时定义的触发器加入到作业中 + foreach (var dbTrigger in dbTriggers) + { + if (schedulerBuilder.GetTriggerBuilder(dbTrigger.TriggerId)?.JobId == jobBuilder.JobId) continue; + var triggerBuilder = TriggerBuilder.Create(dbTrigger.TriggerId).LoadFrom(dbTrigger); + schedulerBuilder.AddTriggerBuilder(triggerBuilder); // 先添加 + triggerBuilder.Updated(); // 再标记更新 + } + + // 标记更新 + schedulerBuilder.Updated(); + } + + // 获取数据库所有通过脚本创建的作业 + var allDbScriptJobs = await db.Queryable().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(); + foreach (var dbDetail in allDbScriptJobs) + { + // 动态创建作业 + Type jobType; + switch (dbDetail.CreateType) + { + case JobCreateTypeEnum.Script: + jobType = dynamicJobCompiler.BuildJob(dbDetail.ScriptCode); + break; + + case JobCreateTypeEnum.Http: + jobType = typeof(HttpJob); + break; + + default: + throw new NotSupportedException(); + } + + // 动态构建的 jobType 的程序集名称为随机名称,需重新设置 + dbDetail.AssemblyName = jobType.Assembly.FullName!.Split(',')[0]; + var jobBuilder = JobBuilder.Create(jobType).LoadFrom(dbDetail); + + // 强行设置为不扫描 IJob 实现类 [Trigger] 特性触发器,否则 SchedulerBuilder.Create 会再次扫描,导致重复添加同名触发器 + jobBuilder.SetIncludeAnnotations(false); + + // 获取作业的所有数据库的触发器加入到作业中 + var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(); + var triggerBuilders = dbTriggers.Select(u => TriggerBuilder.Create(u.TriggerId).LoadFrom(u).Updated()); + var schedulerBuilder = SchedulerBuilder.Create(jobBuilder, triggerBuilders.ToArray()); + + // 标记更新 + schedulerBuilder.Updated(); + + allJobs.Add(schedulerBuilder); + } + + return allJobs; + } + + /// + /// 作业计划初始化通知 + /// + /// + /// + /// + public Task OnLoadingAsync(SchedulerBuilder builder, CancellationToken stoppingToken) + { + return Task.FromResult(builder); + } + + /// + /// 作业计划Scheduler的JobDetail变化时 + /// + /// + public async Task OnChangedAsync(PersistenceContext context) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + + var jobDetail = context.JobDetail.Adapt(); + switch (context.Behavior) + { + case PersistenceBehavior.Appended: + await db.Insertable(jobDetail).ExecuteCommandAsync(); + break; + + case PersistenceBehavior.Updated: + await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync(); + break; + + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync(); + break; + } + } + } + + /// + /// 作业计划Scheduler的触发器Trigger变化时 + /// + /// + public async Task OnTriggerChangedAsync(PersistenceTriggerContext context) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + + var jobTrigger = context.Trigger.Adapt(); + switch (context.Behavior) + { + case PersistenceBehavior.Appended: + await db.Insertable(jobTrigger).ExecuteCommandAsync(); + break; + + case PersistenceBehavior.Updated: + await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync(); + break; + + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync(); + break; + } + } + } + + /// + /// 作业触发器运行记录 + /// + /// + public async Task OnExecutionRecordAsync(TriggerTimeline timeline) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + + var jobTriggerRecord = timeline.Adapt(); + await db.Insertable(jobTriggerRecord).ExecuteCommandAsync(); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailInput.cs b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailInput.cs new file mode 100644 index 00000000..4f22fcc3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailInput.cs @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class JobDetailInput +{ + /// + /// 作业Id + /// + public string JobId { get; set; } +} + +public class PageJobDetailInput : BasePageInput +{ + /// + /// 作业Id + /// + public string JobId { get; set; } + + /// + /// 描述信息 + /// + public string Description { get; set; } +} + +public class AddJobDetailInput : SysJobDetail +{ + /// + /// 作业Id + /// + [Required(ErrorMessage = "作业Id不能为空"), MinLength(2, ErrorMessage = "作业Id不能少于2个字符")] + public override string JobId { get; set; } +} + +public class UpdateJobDetailInput : AddJobDetailInput +{ +} + +public class DeleteJobDetailInput : JobDetailInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailOutput.cs b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailOutput.cs new file mode 100644 index 00000000..a6efa856 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobDetailOutput.cs @@ -0,0 +1,20 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class JobDetailOutput +{ + /// + /// 作业信息 + /// + public SysJobDetail JobDetail { get; set; } + + /// + /// 触发器集合 + /// + public List JobTriggers { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerInput.cs b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerInput.cs new file mode 100644 index 00000000..334f2ba5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerInput.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class JobTriggerInput +{ + /// + /// 作业Id + /// + public string JobId { get; set; } + + /// + /// 触发器Id + /// + public string TriggerId { get; set; } +} + +public class AddJobTriggerInput : SysJobTrigger +{ + /// + /// 作业Id + /// + [Required(ErrorMessage = "作业Id不能为空"), MinLength(2, ErrorMessage = "作业Id不能少于2个字符")] + public override string JobId { get; set; } + + /// + /// 触发器Id + /// + [Required(ErrorMessage = "触发器Id不能为空"), MinLength(2, ErrorMessage = "触发器Id不能少于2个字符")] + public override string TriggerId { get; set; } +} + +public class UpdateJobTriggerInput : AddJobTriggerInput +{ +} + +public class DeleteJobTriggerInput : JobTriggerInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerRecordInput.cs b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerRecordInput.cs new file mode 100644 index 00000000..2b909b27 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/Dto/JobTriggerRecordInput.cs @@ -0,0 +1,20 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PageJobTriggerRecordInput : BasePageInput +{ + /// + /// 作业Id + /// + public string JobId { get; set; } + + /// + /// 触发器Id + /// + public string TriggerId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs new file mode 100644 index 00000000..b9cc448b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs @@ -0,0 +1,107 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 作业集群控制 +/// +public class JobClusterServer : IJobClusterServer +{ + private readonly Random rd = new(DateTime.Now.Millisecond); + + public JobClusterServer() + { + } + + /// + /// 当前作业调度器启动通知 + /// + /// 作业集群服务上下文 + public async void Start(JobClusterContext context) + { + var _sysJobClusterRep = App.GetRequiredService>(); + // 在作业集群表中,如果 clusterId 不存在,则新增一条(否则更新一条),并设置 status 为 ClusterStatus.Waiting + if (await _sysJobClusterRep.IsAnyAsync(u => u.ClusterId == context.ClusterId)) + { + await _sysJobClusterRep.AsUpdateable().SetColumns(u => u.Status == ClusterStatus.Waiting).Where(u => u.ClusterId == context.ClusterId).ExecuteCommandAsync(); + } + else + { + await _sysJobClusterRep.AsInsertable(new SysJobCluster { ClusterId = context.ClusterId, Status = ClusterStatus.Waiting }).ExecuteCommandAsync(); + } + } + + /// + /// 等待被唤醒 + /// + /// 作业集群服务上下文 + /// + public async Task WaitingForAsync(JobClusterContext context) + { + var clusterId = context.ClusterId; + + while (true) + { + // 控制集群心跳频率(放在头部为了防止 IsAnyAsync continue 没sleep占用大量IO和CPU) + await Task.Delay(3000 + rd.Next(500, 1000)); // 错开集群同时启动 + + try + { + ICache _cache = App.GetRequiredService(); + // 使用分布式锁 + using (_cache.AcquireLock("lock:JobClusterServer:WaitingForAsync", 1000)) + { + var _sysJobClusterRep = App.GetRequiredService>(); + // 在这里查询数据库,根据以下两种情况处理 + // 1) 如果作业集群表已有 status 为 ClusterStatus.Working 则继续循环 + // 2) 如果作业集群表中还没有其他服务或只有自己,则插入一条集群服务或调用 await WorkNowAsync(clusterId); 之后 return; + // 3) 如果作业集群表中没有 status 为 ClusterStatus.Working 的,调用 await WorkNowAsync(clusterId); 之后 return; + if (await _sysJobClusterRep.IsAnyAsync(u => u.Status == ClusterStatus.Working)) + continue; + + await WorkNowAsync(clusterId); + return; + } + } + catch { } + } + } + + /// + /// 当前作业调度器停止通知 + /// + /// 作业集群服务上下文 + public async void Stop(JobClusterContext context) + { + var _sysJobClusterRep = App.GetRequiredService>(); + // 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed + await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId); + } + + /// + /// 当前作业调度器宕机 + /// + /// 作业集群服务上下文 + public async void Crash(JobClusterContext context) + { + var _sysJobClusterRep = App.GetRequiredService>(); + // 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed + await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId); + } + + /// + /// 指示集群可以工作 + /// + /// 集群 Id + /// + private static async Task WorkNowAsync(string clusterId) + { + var _sysJobClusterRep = App.GetRequiredService>(); + // 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Working + await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Working }, u => u.ClusterId == clusterId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs b/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs new file mode 100644 index 00000000..eaffcb17 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 作业执行监视器 +/// +public class JobMonitor : IJobMonitor +{ + private readonly IEventPublisher _eventPublisher; + private readonly IServiceScope _serviceScope; + private readonly SysConfigService _sysConfigService; + + public JobMonitor(IServiceScopeFactory scopeFactory) + { + _serviceScope = scopeFactory.CreateScope(); + _sysConfigService = _serviceScope.ServiceProvider.GetRequiredService(); + _eventPublisher = _serviceScope.ServiceProvider.GetRequiredService(); ; + } + + public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + return Task.CompletedTask; + } + + public async Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken) + { + // 将异常作业发送到邮件 + if (await _sysConfigService.GetConfigValue(CommonConst.SysErrorMail) && context.Exception != null) + { + var errorInfo = $"【{context.Trigger.Description}】定时任务错误:{context.Exception}"; + await _eventPublisher.PublishAsync(CommonConst.SendErrorMail, errorInfo); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/SysJobService.cs b/Admin.NET/Admin.NET.Core/Service/Job/SysJobService.cs new file mode 100644 index 00000000..3ba906e5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Job/SysJobService.cs @@ -0,0 +1,359 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统作业任务服务 🧩 +/// +[ApiDescriptionSettings(Order = 320)] +public class SysJobService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysJobDetailRep; + private readonly SqlSugarRepository _sysJobTriggerRep; + private readonly SqlSugarRepository _sysJobTriggerRecordRep; + private readonly SqlSugarRepository _sysJobClusterRep; + private readonly ISchedulerFactory _schedulerFactory; + private readonly DynamicJobCompiler _dynamicJobCompiler; + + public SysJobService(SqlSugarRepository sysJobDetailRep, + SqlSugarRepository sysJobTriggerRep, + SqlSugarRepository sysJobTriggerRecordRep, + SqlSugarRepository sysJobClusterRep, + ISchedulerFactory schedulerFactory, + DynamicJobCompiler dynamicJobCompiler) + { + _sysJobDetailRep = sysJobDetailRep; + _sysJobTriggerRep = sysJobTriggerRep; + _sysJobTriggerRecordRep = sysJobTriggerRecordRep; + _sysJobClusterRep = sysJobClusterRep; + _schedulerFactory = schedulerFactory; + _dynamicJobCompiler = dynamicJobCompiler; + } + + /// + /// 获取作业分页列表 ⏰ + /// + [DisplayName("获取作业分页列表")] + public async Task> PageJobDetail(PageJobDetailInput input) + { + var jobDetails = await _sysJobDetailRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Description), u => u.Description.Contains(input.Description)) + .Select(d => new JobDetailOutput + { + JobDetail = d, + }).ToPagedListAsync(input.Page, input.PageSize); + await _sysJobDetailRep.AsSugarClient().ThenMapperAsync(jobDetails.Items, async u => + { + u.JobTriggers = await _sysJobTriggerRep.GetListAsync(t => t.JobId == u.JobDetail.JobId); + }); + + // 提取中括号里面的参数值 + var rgx = new Regex(@"(?i)(?<=\[)(.*)(?=\])"); + foreach (var job in jobDetails.Items) + { + foreach (var jobTrigger in job.JobTriggers) + { + jobTrigger.Args = rgx.Match(jobTrigger.Args ?? "").Value; + } + } + return jobDetails; + } + + /// + /// 添加作业 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "AddJobDetail"), HttpPost] + [DisplayName("添加作业")] + public async Task AddJobDetail(AddJobDetailInput input) + { + var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1006); + + // 动态创建作业 + Type jobType; + switch (input.CreateType) + { + case JobCreateTypeEnum.Script when string.IsNullOrEmpty(input.ScriptCode): + throw Oops.Oh(ErrorCodeEnum.D1701); + case JobCreateTypeEnum.Script: + { + jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode); + + if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute) + throw Oops.Oh(ErrorCodeEnum.D1702); + if (jobDetailAttribute.JobId != input.JobId) + throw Oops.Oh(ErrorCodeEnum.D1703); + break; + } + case JobCreateTypeEnum.Http: + jobType = typeof(HttpJob); + break; + + default: + throw new NotSupportedException(); + } + + _schedulerFactory.AddJob( + JobBuilder.Create(jobType) + .LoadFrom(input.Adapt()).SetJobType(jobType)); + + // 延迟一下等待持久化写入,再执行其他字段的更新 + await Task.Delay(500); + await _sysJobDetailRep.AsUpdateable() + .SetColumns(u => new SysJobDetail { CreateType = input.CreateType, ScriptCode = input.ScriptCode }) + .Where(u => u.JobId == input.JobId).ExecuteCommandAsync(); + } + + /// + /// 更新作业 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "UpdateJobDetail"), HttpPost] + [DisplayName("更新作业")] + public async Task UpdateJobDetail(UpdateJobDetailInput input) + { + var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1006); + + var sysJobDetail = await _sysJobDetailRep.GetFirstAsync(u => u.Id == input.Id); + if (sysJobDetail.JobId != input.JobId) + throw Oops.Oh(ErrorCodeEnum.D1704); + + var scheduler = _schedulerFactory.GetJob(sysJobDetail.JobId); + var oldScriptCode = sysJobDetail.ScriptCode; // 旧脚本代码 + input.Adapt(sysJobDetail); + + if (input.CreateType == JobCreateTypeEnum.Script) + { + if (string.IsNullOrEmpty(input.ScriptCode)) + throw Oops.Oh(ErrorCodeEnum.D1701); + + if (input.ScriptCode != oldScriptCode) + { + // 动态创建作业 + var jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode); + + if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute) + throw Oops.Oh(ErrorCodeEnum.D1702); + if (jobDetailAttribute.JobId != input.JobId) + throw Oops.Oh(ErrorCodeEnum.D1703); + + scheduler?.UpdateDetail(JobBuilder.Create(jobType).LoadFrom(sysJobDetail).SetJobType(jobType)); + } + } + else + { + scheduler?.UpdateDetail(scheduler.GetJobBuilder().LoadFrom(sysJobDetail)); + } + + // Tip: 假如这次更新有变更了 JobId,变更 JobId 后触发的持久化更新执行,会由于找不到 JobId 而更新不到数据 + // 延迟一下等待持久化写入,再执行其他字段的更新 + await Task.Delay(500); + await _sysJobDetailRep.UpdateAsync(sysJobDetail); + } + + /// + /// 删除作业 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "DeleteJobDetail"), HttpPost] + [DisplayName("删除作业")] + public async Task DeleteJobDetail(DeleteJobDetailInput input) + { + _schedulerFactory.RemoveJob(input.JobId); + + // 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下面的代码确保作业和触发器能被删除 + await _sysJobDetailRep.DeleteAsync(u => u.JobId == input.JobId); + await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId); + } + + /// + /// 获取触发器列表 ⏰ + /// + [DisplayName("获取触发器列表")] + public async Task> GetJobTriggerList([FromQuery] JobDetailInput input) + { + return await _sysJobTriggerRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId)) + .ToListAsync(); + } + + /// + /// 添加触发器 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "AddJobTrigger"), HttpPost] + [DisplayName("添加触发器")] + public async Task AddJobTrigger(AddJobTriggerInput input) + { + var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1006); + + var jobTrigger = input.Adapt(); + jobTrigger.Args = "[" + jobTrigger.Args + "]"; + + var scheduler = _schedulerFactory.GetJob(input.JobId); + scheduler?.AddTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger)); + } + + /// + /// 更新触发器 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "UpdateJobTrigger"), HttpPost] + [DisplayName("更新触发器")] + public async Task UpdateJobTrigger(UpdateJobTriggerInput input) + { + var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1006); + + var jobTrigger = input.Adapt(); + jobTrigger.Args = "[" + jobTrigger.Args + "]"; + + var scheduler = _schedulerFactory.GetJob(input.JobId); + scheduler?.UpdateTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger)); + } + + /// + /// 删除触发器 ⏰ + /// + /// + [ApiDescriptionSettings(Name = "DeleteJobTrigger"), HttpPost] + [DisplayName("删除触发器")] + public async Task DeleteJobTrigger(DeleteJobTriggerInput input) + { + var scheduler = _schedulerFactory.GetJob(input.JobId); + scheduler?.RemoveTrigger(input.TriggerId); + + // 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下行代码确保触发器能被删除 + await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId && u.TriggerId == input.TriggerId); + } + + /// + /// 暂停所有作业 ⏰ + /// + /// + [DisplayName("暂停所有作业")] + public void PauseAllJob() + { + _schedulerFactory.PauseAll(); + } + + /// + /// 启动所有作业 ⏰ + /// + /// + [DisplayName("启动所有作业")] + public void StartAllJob() + { + _schedulerFactory.StartAll(); + } + + /// + /// 暂停作业 ⏰ + /// + [DisplayName("暂停作业")] + public void PauseJob(JobDetailInput input) + { + _schedulerFactory.TryPauseJob(input.JobId, out _); + } + + /// + /// 启动作业 ⏰ + /// + [DisplayName("启动作业")] + public void StartJob(JobDetailInput input) + { + _schedulerFactory.TryStartJob(input.JobId, out _); + } + + /// + /// 取消作业 ⏰ + /// + [DisplayName("取消作业")] + public void CancelJob(JobDetailInput input) + { + _schedulerFactory.TryCancelJob(input.JobId, out _); + } + + /// + /// 执行作业 ⏰ + /// + /// + [DisplayName("执行作业")] + public void RunJob(JobDetailInput input) + { + if (_schedulerFactory.TryRunJob(input.JobId, out _) != ScheduleResult.Succeed) + throw Oops.Oh(ErrorCodeEnum.D1705); + } + + /// + /// 暂停触发器 ⏰ + /// + [DisplayName("暂停触发器")] + public void PauseTrigger(JobTriggerInput input) + { + var scheduler = _schedulerFactory.GetJob(input.JobId); + scheduler?.PauseTrigger(input.TriggerId); + } + + /// + /// 启动触发器 ⏰ + /// + [DisplayName("启动触发器")] + public void StartTrigger(JobTriggerInput input) + { + var scheduler = _schedulerFactory.GetJob(input.JobId); + scheduler?.StartTrigger(input.TriggerId); + } + + /// + /// 强制唤醒作业调度器 ⏰ + /// + [DisplayName("强制唤醒作业调度器")] + public void CancelSleep() + { + _schedulerFactory.CancelSleep(); + } + + /// + /// 强制触发所有作业持久化 ⏰ + /// + [DisplayName("强制触发所有作业持久化")] + public void PersistAll() + { + _schedulerFactory.PersistAll(); + } + + /// + /// 获取集群列表 ⏰ + /// + [DisplayName("获取集群列表")] + public async Task> GetJobClusterList() + { + return await _sysJobClusterRep.GetListAsync(); + } + + /// + /// 获取作业触发器运行记录分页列表 ⏰ + /// + [DisplayName("获取作业触发器运行记录分页列表")] + public async Task> PageJobTriggerRecord(PageJobTriggerRecordInput input) + { + return await _sysJobTriggerRecordRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId)) + .WhereIF(!string.IsNullOrWhiteSpace(input.TriggerId), u => u.TriggerId.Contains(input.TriggerId)) + .OrderByDescending(u => u.Id) + .ToPagedListAsync(input.Page, input.PageSize); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/Dto/ExportLogDto.cs b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/ExportLogDto.cs new file mode 100644 index 00000000..bd99c032 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/ExportLogDto.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 导出日志数据 +/// +[ExcelExporter(Name = "日志数据", TableStyle = OfficeOpenXml.Table.TableStyles.None, AutoFitAllColumn = true)] +public class ExportLogDto +{ + /// + /// 记录器类别名称 + /// + [ExporterHeader(DisplayName = "记录器类别名称", IsBold = true)] + public string LogName { get; set; } + + /// + /// 日志级别 + /// + [ExporterHeader(DisplayName = "日志级别", IsBold = true)] + public string LogLevel { get; set; } + + /// + /// 事件Id + /// + [ExporterHeader(DisplayName = "事件Id", IsBold = true)] + public string EventId { get; set; } + + /// + /// 日志消息 + /// + [ExporterHeader(DisplayName = "日志消息", IsBold = true)] + public string Message { get; set; } + + /// + /// 异常对象 + /// + [ExporterHeader(DisplayName = "异常对象", IsBold = true)] + public string Exception { get; set; } + + /// + /// 当前状态值 + /// + [ExporterHeader(DisplayName = "当前状态值", IsBold = true)] + public string State { get; set; } + + /// + /// 日志记录时间 + /// + [ExporterHeader(DisplayName = "日志记录时间", IsBold = true)] + public DateTime LogDateTime { get; set; } + + /// + /// 线程Id + /// + [ExporterHeader(DisplayName = "线程Id", IsBold = true)] + public int ThreadId { get; set; } + + /// + /// 请求跟踪Id + /// + [ExporterHeader(DisplayName = "请求跟踪Id", IsBold = true)] + public string TraceId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogInput.cs b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogInput.cs new file mode 100644 index 00000000..a51b95cb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogInput.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PageLogInput : BasePageInput +{ + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } +} + +public class LogInput +{ + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogVisOutput.cs b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogVisOutput.cs new file mode 100644 index 00000000..8e69aaf9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/Dto/LogVisOutput.cs @@ -0,0 +1,35 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class LogVisOutput +{ + /// + /// 登录地点 + /// + public string Location { get; set; } + + /// + /// 经度 + /// + public double? Longitude { get; set; } + + /// + /// 维度 + /// + public double? Latitude { get; set; } + + /// + /// 真实姓名 + /// + public string RealName { get; set; } + + /// + /// 日志时间 + /// + public DateTime? LogDateTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/SysLogDiffService.cs b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogDiffService.cs new file mode 100644 index 00000000..755ded76 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogDiffService.cs @@ -0,0 +1,47 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统差异日志服务 🧩 +/// +[ApiDescriptionSettings(Order = 330)] +public class SysLogDiffService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysLogDiffRep; + + public SysLogDiffService(SqlSugarRepository sysLogDiffRep) + { + _sysLogDiffRep = sysLogDiffRep; + } + + /// + /// 获取差异日志分页列表 🔖 + /// + /// + [SuppressMonitor] + [DisplayName("获取差异日志分页列表")] + public async Task> Page(PageLogInput input) + { + return await _sysLogDiffRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime) + .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 清空差异日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Clear"), HttpPost] + [DisplayName("清空差异日志")] + public void Clear() + { + _sysLogDiffRep.AsSugarClient().DbMaintenance.TruncateTable(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/SysLogExService.cs b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogExService.cs new file mode 100644 index 00000000..6fdf825d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogExService.cs @@ -0,0 +1,67 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统异常日志服务 🧩 +/// +[ApiDescriptionSettings(Order = 350)] +public class SysLogExService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysLogExRep; + + public SysLogExService(SqlSugarRepository sysLogExRep) + { + _sysLogExRep = sysLogExRep; + } + + /// + /// 获取异常日志分页列表 🔖 + /// + /// + [SuppressMonitor] + [DisplayName("获取异常日志分页列表")] + public async Task> Page(PageLogInput input) + { + return await _sysLogExRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime) + .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime) + //.OrderBy(u => u.CreateTime, OrderByType.Desc) + .OrderBuilder(input) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 清空异常日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Clear"), HttpPost] + [DisplayName("清空异常日志")] + public void Clear() + { + _sysLogExRep.AsSugarClient().DbMaintenance.TruncateTable(); + } + + /// + /// 导出异常日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Export"), NonUnify] + [DisplayName("导出异常日志")] + public async Task ExportLogEx(LogInput input) + { + var logExList = await _sysLogExRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()), + u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .Select().ToListAsync(); + + IExcelExporter excelExporter = new ExcelExporter(); + var res = await excelExporter.ExportAsByteArray(logExList); + return new FileStreamResult(new MemoryStream(res), "application/octet-stream") { FileDownloadName = DateTime.Now.ToString("yyyyMMddHHmm") + "异常日志.xlsx" }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/SysLogOpService.cs b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogOpService.cs new file mode 100644 index 00000000..45ad9aa6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogOpService.cs @@ -0,0 +1,67 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统操作日志服务 🧩 +/// +[ApiDescriptionSettings(Order = 360)] +public class SysLogOpService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysLogOpRep; + + public SysLogOpService(SqlSugarRepository sysLogOpRep) + { + _sysLogOpRep = sysLogOpRep; + } + + /// + /// 获取操作日志分页列表 🔖 + /// + /// + [SuppressMonitor] + [DisplayName("获取操作日志分页列表")] + public async Task> Page(PageLogInput input) + { + return await _sysLogOpRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime) + .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime) + //.OrderBy(u => u.CreateTime, OrderByType.Desc) + .OrderBuilder(input) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 清空操作日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Clear"), HttpPost] + [DisplayName("清空操作日志")] + public void Clear() + { + _sysLogOpRep.AsSugarClient().DbMaintenance.TruncateTable(); + } + + /// + /// 导出操作日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Export"), NonUnify] + [DisplayName("导出操作日志")] + public async Task ExportLogOp(LogInput input) + { + var logOpList = await _sysLogOpRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()), + u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .Select().ToListAsync(); + + IExcelExporter excelExporter = new ExcelExporter(); + var res = await excelExporter.ExportAsByteArray(logOpList); + return new FileStreamResult(new MemoryStream(res), "application/octet-stream") { FileDownloadName = DateTime.Now.ToString("yyyyMMddHHmm") + "操作日志.xlsx" }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Logging/SysLogVisService.cs b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogVisService.cs new file mode 100644 index 00000000..a08af1a6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Logging/SysLogVisService.cs @@ -0,0 +1,66 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统访问日志服务 🧩 +/// +[ApiDescriptionSettings(Order = 340)] +public class SysLogVisService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysLogVisRep; + + public SysLogVisService(SqlSugarRepository sysLogVisRep) + { + _sysLogVisRep = sysLogVisRep; + } + + /// + /// 获取访问日志分页列表 🔖 + /// + /// + [SuppressMonitor] + [DisplayName("获取访问日志分页列表")] + public async Task> Page(PageLogInput input) + { + return await _sysLogVisRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime) + .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 清空访问日志 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Clear"), HttpPost] + [DisplayName("清空访问日志")] + public void Clear() + { + _sysLogVisRep.AsSugarClient().DbMaintenance.TruncateTable(); + } + + /// + /// 获取访问日志列表 🔖 + /// + /// + [DisplayName("获取访问日志列表")] + public async Task> GetList() + { + return await _sysLogVisRep.AsQueryable() + .Where(u => u.Longitude > 0 && u.Longitude > 0) + .Select(u => new LogVisOutput + { + Location = u.Location, + Longitude = u.Longitude, + Latitude = u.Latitude, + RealName = u.RealName, + LogDateTime = u.LogDateTime + }).ToListAsync(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuInput.cs b/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuInput.cs new file mode 100644 index 00000000..6a3837e0 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuInput.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class MenuInput +{ + /// + /// 标题 + /// + public string Title { get; set; } + + /// + /// 菜单类型(1目录 2菜单 3按钮) + /// + public MenuTypeEnum? Type { get; set; } +} + +public class AddMenuInput : SysMenu +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "菜单名称不能为空")] + public override string Title { get; set; } +} + +public class UpdateMenuInput : AddMenuInput +{ +} + +public class DeleteMenuInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuOutput.cs b/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuOutput.cs new file mode 100644 index 00000000..ab7ad45c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuOutput.cs @@ -0,0 +1,157 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统菜单返回结果 +/// +public class MenuOutput +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 父Id + /// + public long Pid { get; set; } + + /// + /// 菜单类型(0目录 1菜单 2按钮) + /// + public MenuTypeEnum Type { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 路由地址 + /// + public string Path { get; set; } + + /// + /// 组件路径 + /// + public string Component { get; set; } + + /// + /// 权限标识 + /// + public string Permission { get; set; } + + /// + /// 重定向 + /// + public string Redirect { get; set; } + + /// + /// 排序 + /// + public int OrderNo { get; set; } + + /// + /// 状态 + /// + public StatusEnum Status { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + + /// + /// 创建时间 + /// + public virtual DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + public virtual DateTime UpdateTime { get; set; } + + /// + /// 创建者姓名 + /// + public virtual string CreateUserName { get; set; } + + /// + /// 修改者姓名 + /// + public virtual string UpdateUserName { get; set; } + + /// + /// 菜单Meta + /// + public SysMenuMeta Meta { get; set; } + + /// + /// 菜单子项 + /// + public List Children { get; set; } +} + +/// +/// 菜单Meta配置 +/// +public class SysMenuMeta +{ + /// + /// 标题 + /// + public string Title { get; set; } + + /// + /// 图标 + /// + public string Icon { get; set; } + + /// + /// 是否内嵌 + /// + public bool IsIframe { get; set; } + + /// + /// 外链链接 + /// + public string IsLink { get; set; } + + /// + /// 是否隐藏 + /// + public bool IsHide { get; set; } + + /// + /// 是否缓存 + /// + public bool IsKeepAlive { get; set; } + + /// + /// 是否固定 + /// + public bool IsAffix { get; set; } +} + +/// +/// 配置菜单对象映射 +/// +public class SysMenuMapper : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.ForType() + .Map(t => t.Meta.Title, o => o.Title) + .Map(t => t.Meta.Icon, o => o.Icon) + .Map(t => t.Meta.IsIframe, o => o.IsIframe) + .Map(t => t.Meta.IsLink, o => o.OutLink) + .Map(t => t.Meta.IsHide, o => o.IsHide) + .Map(t => t.Meta.IsKeepAlive, o => o.IsKeepAlive) + .Map(t => t.Meta.IsAffix, o => o.IsAffix); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs new file mode 100644 index 00000000..4349ef44 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs @@ -0,0 +1,292 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统菜单服务 🧩 +/// +[ApiDescriptionSettings(Order = 450)] +public class SysMenuService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysMenuRep; + private readonly SysRoleMenuService _sysRoleMenuService; + private readonly SysUserRoleService _sysUserRoleService; + private readonly SysCacheService _sysCacheService; + + public SysMenuService(UserManager userManager, + SqlSugarRepository sysMenuRep, + SysRoleMenuService sysRoleMenuService, + SysUserRoleService sysUserRoleService, + SysCacheService sysCacheService) + { + _userManager = userManager; + _sysMenuRep = sysMenuRep; + _sysRoleMenuService = sysRoleMenuService; + _sysUserRoleService = sysUserRoleService; + _sysCacheService = sysCacheService; + } + + /// + /// 获取登录菜单树 🔖 + /// + /// + [DisplayName("获取登录菜单树")] + public async Task> GetLoginMenuTree() + { + if (_userManager.SuperAdmin) + { + var menuList = await _sysMenuRep.AsQueryable() + .Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable) + .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0); + return menuList.Adapt>(); + } + else + { + var menuIdList = await GetMenuIdList(); + var menuTree = await _sysMenuRep.AsQueryable() + .Where(u => u.Status == StatusEnum.Enable) + .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); + DeleteBtnFromMenuTree(menuTree); + return menuTree.Adapt>(); + } + } + + /// + /// 删除登录菜单树里面的按钮 + /// + private void DeleteBtnFromMenuTree(List menuList) + { + if (menuList == null) return; + for (var i = menuList.Count - 1; i >= 0; i--) + { + var menu = menuList[i]; + if (menu.Type == MenuTypeEnum.Btn) + menuList.Remove(menu); + else if (menu.Children.Count > 0) + DeleteBtnFromMenuTree(menu.Children); + } + } + + /// + /// 获取菜单列表 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("获取菜单列表")] + public async Task> GetList([FromQuery] MenuInput input) + { + var menuIdList = _userManager.SuperAdmin ? new List() : await GetMenuIdList(); + + // 有筛选条件时返回list列表(防止构造不出树) + if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0) + { + return await _sysMenuRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title)) + .WhereIF(input.Type is > 0, u => u.Type == input.Type) + .WhereIF(menuIdList.Count > 1, u => menuIdList.Contains(u.Id)) + .OrderBy(u => u.OrderNo).ToListAsync(); + } + + return _userManager.SuperAdmin ? + await _sysMenuRep.AsQueryable().OrderBy(u => u.OrderNo).ToTreeAsync(u => u.Children, u => u.Pid, 0) : + await _sysMenuRep.AsQueryable() + .OrderBy(u => u.OrderNo).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); // 角色菜单授权时 + } + + /// + /// 增加菜单 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加菜单")] + public async Task AddMenu(AddMenuInput input) + { + var isExist = input.Type != MenuTypeEnum.Btn + ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title && u.Pid == input.Pid) + : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission && u.Pid == input.Pid); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D4000); + + if (!string.IsNullOrWhiteSpace(input.Name)) + { + if (await _sysMenuRep.IsAnyAsync(u => u.Name == input.Name)) + throw Oops.Oh(ErrorCodeEnum.D4009); + } + + if (input.Pid != 0) + { + if (await _sysMenuRep.IsAnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn)) + throw Oops.Oh(ErrorCodeEnum.D4010); + } + + // 校验菜单参数 + var sysMenu = input.Adapt(); + CheckMenuParam(sysMenu); + + await _sysMenuRep.InsertAsync(sysMenu); + + // 清除缓存 + DeleteMenuCache(); + } + + /// + /// 更新菜单 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新菜单")] + public async Task UpdateMenu(UpdateMenuInput input) + { + if (input.Id == input.Pid) + throw Oops.Oh(ErrorCodeEnum.D4008); + + var isExist = input.Type != MenuTypeEnum.Btn + ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title && u.Type == input.Type && u.Pid == input.Pid && u.Id != input.Id) + : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission && u.Type == input.Type && u.Pid == input.Pid && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D4000); + + if (!string.IsNullOrWhiteSpace(input.Name)) + { + if (await _sysMenuRep.IsAnyAsync(u => u.Id != input.Id && u.Name == input.Name)) + throw Oops.Oh(ErrorCodeEnum.D4009); + } + + if (input.Pid != 0) + { + if (await _sysMenuRep.IsAnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn)) + throw Oops.Oh(ErrorCodeEnum.D4010); + } + + // 校验菜单参数 + var sysMenu = input.Adapt(); + CheckMenuParam(sysMenu); + + await _sysMenuRep.AsUpdateable(sysMenu).ExecuteCommandAsync(); + + // 清除缓存 + DeleteMenuCache(); + } + + /// + /// 删除菜单 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除菜单")] + public async Task DeleteMenu(DeleteMenuInput input) + { + var menuTreeList = await _sysMenuRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); + var menuIdList = menuTreeList.Select(u => u.Id).ToList(); + + await _sysMenuRep.DeleteAsync(u => menuIdList.Contains(u.Id)); + + // 级联删除角色菜单数据 + await _sysRoleMenuService.DeleteRoleMenuByMenuIdList(menuIdList); + + // 清除缓存 + DeleteMenuCache(); + } + + /// + /// 增加和编辑时检查菜单数据 + /// + /// + private static void CheckMenuParam(SysMenu menu) + { + var permission = menu.Permission; + if (menu.Type == MenuTypeEnum.Btn) + { + menu.Name = null; + menu.Path = null; + menu.Component = null; + menu.Icon = null; + menu.Redirect = null; + menu.OutLink = null; + menu.IsHide = false; + menu.IsKeepAlive = true; + menu.IsAffix = false; + menu.IsIframe = false; + + if (string.IsNullOrEmpty(permission)) + throw Oops.Oh(ErrorCodeEnum.D4003); + if (!permission.Contains(':')) + throw Oops.Oh(ErrorCodeEnum.D4004); + } + else + { + menu.Permission = null; + } + } + + /// + /// 获取用户拥有按钮权限集合(缓存) 🔖 + /// + /// + [DisplayName("获取按钮权限集合")] + public async Task> GetOwnBtnPermList() + { + var userId = _userManager.UserId; + var permissions = _sysCacheService.Get>(CacheConst.KeyUserButton + userId); + if (permissions == null) + { + var menuIdList = _userManager.SuperAdmin ? new List() : await GetMenuIdList(); + permissions = menuIdList.Count > 0 || _userManager.SuperAdmin + ? await _sysMenuRep.AsQueryable() + .Where(u => u.Type == MenuTypeEnum.Btn) + .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id)) + .Select(u => u.Permission).ToListAsync() + : new List(); + _sysCacheService.Set(CacheConst.KeyUserButton + userId, permissions, TimeSpan.FromDays(7)); + } + + return permissions; + } + + /// + /// 获取系统所有按钮权限集合(缓存) + /// + /// + [NonAction] + public async Task> GetAllBtnPermList() + { + var permissions = _sysCacheService.Get>(CacheConst.KeyUserButton + 0); + if (permissions == null || permissions.Count == 0) + { + permissions = await _sysMenuRep.AsQueryable() + .Where(u => u.Type == MenuTypeEnum.Btn) + .Select(u => u.Permission).ToListAsync(); + _sysCacheService.Set(CacheConst.KeyUserButton + 0, permissions); + } + + return permissions; + } + + /// + /// 清除菜单和按钮缓存 + /// + private void DeleteMenuCache() + { + // _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserMenu); + _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserButton); + } + + /// + /// 获取当前用户菜单Id集合 + /// + /// + private async Task> GetMenuIdList() + { + var roleIdList = await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId); + return await _sysRoleMenuService.GetRoleMenuIdList(roleIdList); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Message/Dto/MessageInput.cs b/Admin.NET/Admin.NET.Core/Service/Message/Dto/MessageInput.cs new file mode 100644 index 00000000..0501ec76 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Message/Dto/MessageInput.cs @@ -0,0 +1,55 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public class MessageInput +{ + /// + /// 接收者用户Id + /// + public long ReceiveUserId { get; set; } + + /// + /// 接收者名称 + /// + public string ReceiveUserName { get; set; } + + /// + /// 用户ID列表 + /// + public List UserIds { get; set; } + + /// + /// 消息标题 + /// + public string Title { get; set; } + + /// + /// 消息类型 + /// + public MessageTypeEnum MessageType { get; set; } + + /// + /// 消息内容 + /// + public string Message { get; set; } + + /// + /// 发送者Id + /// + public string SendUserId{ get; set; } + + /// + /// 发送者名称 + /// + public string SendUserName { get; set; } + + /// + /// 发送时间 + /// + public DateTime SendTime { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs new file mode 100644 index 00000000..e136ab2e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs @@ -0,0 +1,51 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using MailKit.Net.Smtp; +using MimeKit; + +namespace Admin.NET.Core.Service; + +/// +/// 系统邮件发送服务 🧩 +/// +[ApiDescriptionSettings(Order = 370)] +public class SysEmailService : IDynamicApiController, ITransient +{ + private readonly EmailOptions _emailOptions; + + public SysEmailService(IOptions emailOptions) + { + _emailOptions = emailOptions.Value; + } + + /// + /// 发送邮件 📧 + /// + /// + /// + /// + [DisplayName("发送邮件")] + public async Task SendEmail([Required] string content, string title = "Admin.NET 系统邮件") + { + var message = new MimeMessage(); + message.From.Add(new MailboxAddress(_emailOptions.DefaultFromEmail, _emailOptions.DefaultFromEmail)); + message.To.Add(new MailboxAddress(_emailOptions.DefaultToEmail, _emailOptions.DefaultToEmail)); + message.Subject = title; + message.Body = new TextPart("html") + { + Text = content + }; + + using var client = new SmtpClient(); + client.Connect(_emailOptions.Host, _emailOptions.Port, _emailOptions.EnableSsl); + client.Authenticate(_emailOptions.UserName, _emailOptions.Password); + client.Send(message); + client.Disconnect(true); + + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs new file mode 100644 index 00000000..9d53d38b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs @@ -0,0 +1,127 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.SignalR; + +namespace Admin.NET.Core.Service; + +/// +/// 系统消息发送服务 🧩 +/// +[ApiDescriptionSettings(Order = 370)] +public class SysMessageService : IDynamicApiController, ITransient +{ + private readonly SysCacheService _sysCacheService; + private readonly IHubContext _chatHubContext; + private readonly SysConfigService _sysConfigService; + + public SysMessageService(SysCacheService sysCacheService, + IHubContext chatHubContext, + SysConfigService sysConfigService) + { + _sysCacheService = sysCacheService; + _chatHubContext = chatHubContext; + _sysConfigService = sysConfigService; + } + + /// + /// 发送消息给所有人 🔖 + /// + /// + /// + [DisplayName("发送消息给所有人")] + public async Task SendAllUser(MessageInput input) + { + await _chatHubContext.Clients.All.ReceiveMessage(input); + } + + /// + /// 发送消息给除了发送人的其他人 🔖 + /// + /// + /// + [DisplayName("发送消息给除了发送人的其他人")] + public async Task SendOtherUser(MessageInput input) + { + var cacheKey = CacheConst.KeyUserOnline + input.ReceiveUserId; + // 是否开启单用户登录 + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + var user = _sysCacheService.Get(cacheKey); + if (user == null) return; + await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input); + } + else + { + var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray(); + foreach (var _cacheKey in _cacheKeys) + { + var user = _sysCacheService.Get(_cacheKey); + if (user == null) return; + await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input); + } + } + } + + /// + /// 发送消息给某个人 🔖 + /// + /// + /// + [DisplayName("发送消息给某个人")] + public async Task SendUser(MessageInput input) + { + var cacheKey = CacheConst.KeyUserOnline + input.ReceiveUserId; + // 是否开启单用户登录 + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + var user = _sysCacheService.Get(cacheKey); + if (user == null) return; + await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input); + } + else + { + var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray(); + foreach (var _cacheKey in _cacheKeys) + { + var user = _sysCacheService.Get(_cacheKey); + if (user == null) return; + await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input); + } + } + } + + /// + /// 发送消息给某些人 🔖 + /// + /// + /// + [DisplayName("发送消息给某些人")] + public async Task SendUsers(MessageInput input) + { + var userList = new List(); + foreach (var userId in input.UserIds) + { + var cacheKey = CacheConst.KeyUserOnline + userId; + // 是否开启单用户登录 + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + var user = _sysCacheService.Get(cacheKey); + if (user != null) userList.Add(user.ConnectionId); + } + else + { + var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray(); + foreach (var _cacheKey in _cacheKeys) + { + var user = _sysCacheService.Get(_cacheKey); + if (user != null) userList.Add(user.ConnectionId); + } + } + } + await _chatHubContext.Clients.Clients(userList).ReceiveMessage(input); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs new file mode 100644 index 00000000..466c962d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs @@ -0,0 +1,163 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using AlibabaCloud.SDK.Dysmsapi20170525.Models; +using TencentCloud.Common; +using TencentCloud.Common.Profile; +using TencentCloud.Sms.V20190711; + +namespace Admin.NET.Core.Service; + +/// +/// 系统短信服务 🧩 +/// +[AllowAnonymous] +[ApiDescriptionSettings(Order = 150)] +public class SysSmsService : IDynamicApiController, ITransient +{ + private readonly SMSOptions _smsOptions; + private readonly SysCacheService _sysCacheService; + + public SysSmsService(IOptions smsOptions, + SysCacheService sysCacheService) + { + _smsOptions = smsOptions.Value; + _sysCacheService = sysCacheService; + } + + /// + /// 发送短信 📨 + /// + /// + /// + [AllowAnonymous] + [DisplayName("发送短信")] + public async Task SendSms([Required] string phoneNumber) + { + if (!string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeyId) && !string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeySecret)) + await AliyunSendSms(phoneNumber); + else + await TencentSendSms(phoneNumber); + } + + /// + /// 阿里云发送短信 📨 + /// + /// + /// + [AllowAnonymous] + [DisplayName("阿里云发送短信")] + public async Task AliyunSendSms([Required] string phoneNumber) + { + if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid) + throw Oops.Oh("请正确填写手机号码"); + + // 生成随机验证码 + var random = new Random(); + var verifyCode = random.Next(100000, 999999); + + var templateParam = Clay.Object(new + { + code = verifyCode + }); + + var client = CreateAliyunClient(); + var sendSmsRequest = new SendSmsRequest + { + PhoneNumbers = phoneNumber, // 待发送手机号, 多个以逗号分隔 + SignName = _smsOptions.Aliyun.SignName, // 短信签名 + TemplateCode = _smsOptions.Aliyun.TemplateCode, // 短信模板 + TemplateParam = templateParam.ToString(), // 模板中的变量替换JSON串 + OutId = YitIdHelper.NextId().ToString() + }; + var sendSmsResponse = client.SendSms(sendSmsRequest); + if (sendSmsResponse.Body.Code == "OK" && sendSmsResponse.Body.Message == "OK") + { + // var bizId = sendSmsResponse.Body.BizId; + _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(60)); + } + else + { + throw Oops.Oh($"短信发送失败:{sendSmsResponse.Body.Code}-{sendSmsResponse.Body.Message}"); + } + + await Task.CompletedTask; + } + + /// + /// 腾讯云发送短信 📨 + /// + /// + /// + [AllowAnonymous] + [DisplayName("腾讯云发送短信")] + public async Task TencentSendSms([Required] string phoneNumber) + { + if (!phoneNumber.TryValidate(ValidationTypes.PhoneNumber).IsValid) + throw Oops.Oh("请正确填写手机号码"); + + // 生成随机验证码 + var random = new Random(); + var verifyCode = random.Next(100000, 999999); + + // 实例化要请求产品的client对象,clientProfile是可选的 + var client = new SmsClient(CreateTencentClient(), "ap-guangzhou", new ClientProfile() { HttpProfile = new HttpProfile() { Endpoint = ("sms.tencentcloudapi.com") } }); + // 实例化一个请求对象,每个接口都会对应一个request对象 + var req = new TencentCloud.Sms.V20190711.Models.SendSmsRequest + { + PhoneNumberSet = new string[] { "+86" + phoneNumber.Trim(',') }, + SmsSdkAppid = _smsOptions.Tencentyun.SdkAppId, + Sign = _smsOptions.Tencentyun.SignName, + TemplateID = _smsOptions.Tencentyun.TemplateCode, + TemplateParamSet = new string[] { verifyCode.ToString() } + }; + + // 返回的resp是一个SendSmsResponse的实例,与请求对象对应 + TencentCloud.Sms.V20190711.Models.SendSmsResponse resp = client.SendSmsSync(req); + + if (resp.SendStatusSet[0].Code == "Ok" && resp.SendStatusSet[0].Message == "send success") + { + // var bizId = sendSmsResponse.Body.BizId; + _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(60)); + } + else + { + throw Oops.Oh($"短信发送失败:{resp.SendStatusSet[0].Code}-{resp.SendStatusSet[0].Message}"); + } + + await Task.CompletedTask; + } + + /// + /// 阿里云短信配置 + /// + /// + private AlibabaCloud.SDK.Dysmsapi20170525.Client CreateAliyunClient() + { + var config = new AlibabaCloud.OpenApiClient.Models.Config + { + AccessKeyId = _smsOptions.Aliyun.AccessKeyId, + AccessKeySecret = _smsOptions.Aliyun.AccessKeySecret, + Endpoint = "dysmsapi.aliyuncs.com" + }; + return new AlibabaCloud.SDK.Dysmsapi20170525.Client(config); + } + + /// + /// 腾讯云短信配置 + /// + /// + private Credential CreateTencentClient() + { + var cred = new Credential + { + SecretId = _smsOptions.Tencentyun.AccessKeyId, + SecretKey = _smsOptions.Tencentyun.AccessKeySecret + }; + + return cred; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeInput.cs b/Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeInput.cs new file mode 100644 index 00000000..e4339277 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeInput.cs @@ -0,0 +1,36 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PageNoticeInput : BasePageInput +{ + /// + /// 标题 + /// + public virtual string Title { get; set; } + + /// + /// 类型(1通知 2公告) + /// + public virtual NoticeTypeEnum? Type { get; set; } +} + +public class AddNoticeInput : SysNotice +{ +} + +public class UpdateNoticeInput : AddNoticeInput +{ +} + +public class DeleteNoticeInput : BaseIdInput +{ +} + +public class NoticeInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs b/Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs new file mode 100644 index 00000000..c5896ba2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs @@ -0,0 +1,178 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统通知公告服务 🧩 +/// +[ApiDescriptionSettings(Order = 380)] +public class SysNoticeService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysUserRep; + private readonly SqlSugarRepository _sysNoticeRep; + private readonly SqlSugarRepository _sysNoticeUserRep; + private readonly SysOnlineUserService _sysOnlineUserService; + + public SysNoticeService( + UserManager userManager, + SqlSugarRepository sysUserRep, + SqlSugarRepository sysNoticeRep, + SqlSugarRepository sysNoticeUserRep, + SysOnlineUserService sysOnlineUserService) + { + _userManager = userManager; + _sysUserRep = sysUserRep; + _sysNoticeRep = sysNoticeRep; + _sysNoticeUserRep = sysNoticeUserRep; + _sysOnlineUserService = sysOnlineUserService; + } + + /// + /// 获取通知公告分页列表 📢 + /// + /// + /// + [DisplayName("获取通知公告分页列表")] + public async Task> Page(PageNoticeInput input) + { + return await _sysNoticeRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title.Trim())) + .WhereIF(input.Type > 0, u => u.Type == input.Type) + .WhereIF(!_userManager.SuperAdmin, u => u.CreateUserId == _userManager.UserId) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加通知公告 📢 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加通知公告")] + public async Task AddNotice(AddNoticeInput input) + { + var notice = input.Adapt(); + InitNoticeInfo(notice); + await _sysNoticeRep.InsertAsync(notice); + } + + /// + /// 更新通知公告 📢 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新通知公告")] + public async Task UpdateNotice(UpdateNoticeInput input) + { + var notice = input.Adapt(); + InitNoticeInfo(notice); + await _sysNoticeRep.UpdateAsync(notice); + } + + /// + /// 删除通知公告 📢 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除通知公告")] + public async Task DeleteNotice(DeleteNoticeInput input) + { + await _sysNoticeRep.DeleteAsync(u => u.Id == input.Id); + + await _sysNoticeUserRep.DeleteAsync(u => u.NoticeId == input.Id); + } + + /// + /// 发布通知公告 📢 + /// + /// + /// + [DisplayName("发布通知公告")] + public async Task Public(NoticeInput input) + { + // 更新发布状态和时间 + await _sysNoticeRep.UpdateAsync(u => new SysNotice() { Status = NoticeStatusEnum.PUBLIC, PublicTime = DateTime.Now }, u => u.Id == input.Id); + + var notice = await _sysNoticeRep.GetFirstAsync(u => u.Id == input.Id); + + // 通知到的人(所有账号) + var userIdList = await _sysUserRep.AsQueryable().Select(u => u.Id).ToListAsync(); + + await _sysNoticeUserRep.DeleteAsync(u => u.NoticeId == notice.Id); + var noticeUserList = userIdList.Select(u => new SysNoticeUser + { + NoticeId = notice.Id, + UserId = u, + }).ToList(); + await _sysNoticeUserRep.InsertRangeAsync(noticeUserList); + + // 广播所有在线账号 + await _sysOnlineUserService.PublicNotice(notice, userIdList); + } + + /// + /// 设置通知公告已读状态 📢 + /// + /// + /// + [DisplayName("设置通知公告已读状态")] + public async Task SetRead(NoticeInput input) + { + await _sysNoticeUserRep.UpdateAsync(u => new SysNoticeUser + { + ReadStatus = NoticeUserStatusEnum.READ, + ReadTime = DateTime.Now + }, u => u.NoticeId == input.Id && u.UserId == _userManager.UserId); + } + + /// + /// 获取接收的通知公告 + /// + /// + /// + [DisplayName("获取接收的通知公告")] + public async Task> GetPageReceived([FromQuery] PageNoticeInput input) + { + return await _sysNoticeUserRep.AsQueryable().Includes(u => u.SysNotice) + .Where(u => u.UserId == _userManager.UserId) + .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.SysNotice.Title.Contains(input.Title.Trim())) + .WhereIF(input.Type is > 0, u => u.SysNotice.Type == input.Type) + .OrderBy(u => u.SysNotice.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取未读的通知公告 📢 + /// + /// + [DisplayName("获取未读的通知公告")] + public async Task> GetUnReadList() + { + var noticeUserList = await _sysNoticeUserRep.AsQueryable().Includes(u => u.SysNotice) + .Where(u => u.UserId == _userManager.UserId && u.ReadStatus == NoticeUserStatusEnum.UNREAD) + .OrderBy(u => u.SysNotice.CreateTime, OrderByType.Desc).ToListAsync(); + return noticeUserList.Select(t => t.SysNotice).ToList(); + } + + /// + /// 初始化通知公告信息 + /// + /// + [NonAction] + private void InitNoticeInfo(SysNotice notice) + { + notice.PublicUserId = _userManager.UserId; + notice.PublicUserName = _userManager.RealName; + notice.PublicOrgId = _userManager.OrgId; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs new file mode 100644 index 00000000..b210421e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core.Service; + +public static class HttpContextExtension +{ + public static async Task GetExternalProvidersAsync(this HttpContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var schemes = context.RequestServices.GetRequiredService(); + + return (from scheme in await schemes.GetAllSchemesAsync() + where !string.IsNullOrEmpty(scheme.DisplayName) + select scheme).ToArray(); + } + + public static async Task IsProviderSupportedAsync(this HttpContext context, string provider) + { + ArgumentNullException.ThrowIfNull(context); + + return (from scheme in await context.GetExternalProvidersAsync() + where string.Equals(scheme.Name, provider, StringComparison.OrdinalIgnoreCase) + select scheme).Any(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthClaim.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthClaim.cs new file mode 100644 index 00000000..db892290 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthClaim.cs @@ -0,0 +1,12 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public static class OAuthClaim +{ + public const string GiteeAvatarUrl = "urn:gitee:avatar_url"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthSetup.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthSetup.cs new file mode 100644 index 00000000..4c156d5f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/OAuthSetup.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; + +namespace Admin.NET.Core; + +public static class OAuthSetup +{ + /// + /// 三方授权登录OAuth注册 + /// + /// + public static void AddOAuth(this IServiceCollection services) + { + var authOpt = App.GetConfig("OAuth", true); + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddCookie(options => + { + options.Cookie.SameSite = SameSiteMode.Lax; + }) + .AddWeixin(options => + { + options.ClientId = authOpt.Weixin?.ClientId; + options.ClientSecret = authOpt.Weixin?.ClientSecret; + }) + .AddGitee(options => + { + options.ClientId = authOpt.Gitee?.ClientId; + options.ClientSecret = authOpt.Gitee?.ClientSecret; + + options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url"); + }); + } + + public static void UseOAuth(this IApplicationBuilder app) + { + app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs new file mode 100644 index 00000000..b10b559b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs @@ -0,0 +1,117 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; +using System.Security.Claims; + +namespace Admin.NET.Core.Service; + +/// +/// 系统OAuth服务 🧩 +/// +[AllowAnonymous] +[ApiDescriptionSettings(Order = 498)] +public class SysOAuthService : IDynamicApiController, ITransient +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly SqlSugarRepository _sysOAuthUserRep; + + public SysOAuthService(IHttpContextAccessor httpContextAccessor, + SqlSugarRepository sysOAuthUserRep) + { + _httpContextAccessor = httpContextAccessor; + _sysOAuthUserRep = sysOAuthUserRep; + } + + /// + /// 第三方登录 🔖 + /// + /// + /// + /// + [ApiDescriptionSettings(Name = "SignIn"), HttpGet] + [DisplayName("第三方登录")] + public virtual async Task SignIn([FromQuery] string provider, [FromQuery] string redirectUrl) + { + if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider)) + throw Oops.Oh("不支持的OAuth类型"); + + var request = _httpContextAccessor.HttpContext.Request; + var url = $"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}Callback?provider={provider}&redirectUrl={redirectUrl}"; + var properties = new AuthenticationProperties { RedirectUri = url }; + properties.Items["LoginProvider"] = provider; + return await Task.FromResult(new ChallengeResult(provider, properties)); + } + + /// + /// 授权回调 🔖 + /// + /// + /// + /// + [ApiDescriptionSettings(Name = "SignInCallback"), HttpGet] + [DisplayName("授权回调")] + public virtual async Task SignInCallback([FromQuery] string provider = null, [FromQuery] string redirectUrl = "") + { + if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider)) + throw Oops.Oh("不支持的OAuth类型"); + + var authenticateResult = await _httpContextAccessor.HttpContext.AuthenticateAsync(provider); + if (!authenticateResult.Succeeded) + throw Oops.Oh("授权失败"); + + var openIdClaim = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier); + if (openIdClaim == null || string.IsNullOrWhiteSpace(openIdClaim.Value)) + throw Oops.Oh("授权失败"); + + var name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value; + var email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value; + var mobilePhone = authenticateResult.Principal.FindFirst(ClaimTypes.MobilePhone)?.Value; + var dateOfBirth = authenticateResult.Principal.FindFirst(ClaimTypes.DateOfBirth)?.Value; + var gender = authenticateResult.Principal.FindFirst(ClaimTypes.Gender)?.Value; + var avatarUrl = ""; + + var platformType = PlatformTypeEnum.微信公众号; + if (provider == "Gitee") + { + platformType = PlatformTypeEnum.Gitee; + avatarUrl = authenticateResult.Principal.FindFirst(OAuthClaim.GiteeAvatarUrl)?.Value; + } + + // 若账号不存在则新建 + var wechatUser = await _sysOAuthUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value); + if (wechatUser == null) + { + var userId = await App.GetRequiredService().AddUser(new AddUserInput() + { + Account = name, + RealName = name, + NickName = name, + Email = email, + Avatar = avatarUrl, + Phone = mobilePhone, + OrgId = 1300000000101, // 根组织架构 + RoleIdList = new List { 1300000000104 } // 仅本人数据角色 + }); + + await _sysOAuthUserRep.InsertAsync(new SysOAuthUser() + { + UserId = userId, + OpenId = openIdClaim.Value, + Avatar = avatarUrl, + NickName = name, + PlatformType = platformType + }); + + wechatUser = await _sysOAuthUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value); + } + + // 构建Token令牌 + var token = await App.GetRequiredService().CreateToken(wechatUser.SysUser); + + return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}"); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserInput.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserInput.cs new file mode 100644 index 00000000..89156741 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserInput.cs @@ -0,0 +1,29 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class OAuthUserInput : BasePageInput +{ + /// + /// 账号 + /// + public string Account { get; set; } + + /// + /// 昵称 + /// + public string NickName { get; set; } + + /// + /// 手机号码 + /// + public string Mobile { get; set; } +} + +public class DeleteOAuthUserInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserOutput.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserOutput.cs new file mode 100644 index 00000000..83df83c6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/Dto/OAuthUserOutput.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class OAuthUserOutput : SysOAuthUser +{ + /// + /// 邮箱 + /// + [Newtonsoft.Json.JsonConverter(typeof(MaskEmailNewtonsoftJsonConverter))] + public override string? Email { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/SysOAuthUserService.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/SysOAuthUserService.cs new file mode 100644 index 00000000..06d16ae1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthUser/SysOAuthUserService.cs @@ -0,0 +1,75 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统OAuth账号服务 🧩 +/// +[ApiDescriptionSettings(Order = 488)] +public class SysOAuthUserService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysOAuthUserRep; + + public SysOAuthUserService(SqlSugarRepository sysOAuthUserRep) + { + _sysOAuthUserRep = sysOAuthUserRep; + } + + /// + /// 获取OAuth账号列表 🔖 + /// + /// + /// + [DisplayName("获取OAuth账号列表")] + public async Task> Page(OAuthUserInput input) + { + return await _sysOAuthUserRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account.Contains(input.Account)) + .WhereIF(!string.IsNullOrWhiteSpace(input.NickName), u => u.NickName.Contains(input.NickName)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), u => u.Mobile.Contains(input.Mobile)) + .OrderBy(u => u.Id, OrderByType.Desc) + .Select() + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加OAuth账号 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加OAuth账号")] + public async Task AddWechatUser(SysOAuthUser input) + { + await _sysOAuthUserRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新OAuth账号 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新OAuth账号")] + public async Task UpdateWechatUser(SysOAuthUser input) + { + var weChatUser = input.Adapt(); + await _sysOAuthUserRep.AsUpdateable(weChatUser).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除OAuth账号 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除OAuth账号")] + public async Task DeleteWechatUser(DeleteOAuthUserInput input) + { + await _sysOAuthUserRep.DeleteAsync(u => u.Id == input.Id); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OnlineUser/Dto/OnlineUserInput.cs b/Admin.NET/Admin.NET.Core/Service/OnlineUser/Dto/OnlineUserInput.cs new file mode 100644 index 00000000..d96aea0d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OnlineUser/Dto/OnlineUserInput.cs @@ -0,0 +1,20 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PageOnlineUserInput : BasePageInput +{ + /// + /// 账号名称 + /// + public string UserName { get; set; } + + /// + /// 真实姓名 + /// + public string RealName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs new file mode 100644 index 00000000..f8170d68 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs @@ -0,0 +1,105 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.SignalR; + +namespace Admin.NET.Core.Service; + +/// +/// 系统在线用户服务 🧩 +/// +[ApiDescriptionSettings(Order = 300)] +public class SysOnlineUserService : IDynamicApiController, ITransient +{ + private readonly SysConfigService _sysConfigService; + private readonly IHubContext _onlineUserHubContext; + private readonly SqlSugarRepository _sysOnlineUerRep; + + public SysOnlineUserService(SysConfigService sysConfigService, + IHubContext onlineUserHubContext, + SqlSugarRepository sysOnlineUerRep) + { + _sysConfigService = sysConfigService; + _onlineUserHubContext = onlineUserHubContext; + _sysOnlineUerRep = sysOnlineUerRep; + } + + /// + /// 获取在线用户分页列表 🔖 + /// + /// + [DisplayName("获取在线用户分页列表")] + public async Task> Page(PageOnlineUserInput input) + { + return await _sysOnlineUerRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), u => u.UserName.Contains(input.UserName)) + .WhereIF(!string.IsNullOrWhiteSpace(input.RealName), u => u.RealName.Contains(input.RealName)) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 强制下线 🔖 + /// + /// + /// + [NonValidation] + [DisplayName("强制下线")] + public async Task ForceOffline(SysOnlineUser user) + { + await _onlineUserHubContext.Clients.Client(user.ConnectionId).ForceOffline("强制下线"); + await _sysOnlineUerRep.DeleteAsync(user); + } + + /// + /// 发布站内消息 + /// + /// + /// + /// + [NonAction] + public async Task PublicNotice(SysNotice notice, List userIds) + { + var userList = await _sysOnlineUerRep.GetListAsync(u => userIds.Contains(u.UserId)); + if (userList.Count == 0) return; + + foreach (var item in userList) + { + await _onlineUserHubContext.Clients.Client(item.ConnectionId).PublicNotice(notice); + } + } + + /// + /// 单用户登录 + /// + /// + [NonAction] + public async Task SingleLogin(long userId) + { + if (await _sysConfigService.GetConfigValue(CommonConst.SysSingleLogin)) + { + var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId); + foreach (var user in users) + { + await ForceOffline(user); + } + } + } + + /// + /// 通过用户ID踢掉在线用户 + /// + /// + /// + [NonAction] + public async Task ForceOffline(long userId) + { + var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId); + foreach (var user in users) + { + await ForceOffline(user); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs new file mode 100644 index 00000000..a3d57932 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs @@ -0,0 +1,85 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 开放接口身份输入参数 +/// +public class OpenAccessInput : BasePageInput +{ + /// + /// 身份标识 + /// + public string AccessKey { get; set; } +} + +public class AddOpenAccessInput : SysOpenAccess +{ + /// + /// 身份标识 + /// + [Required(ErrorMessage = "身份标识不能为空")] + public override string AccessKey { get; set; } + + /// + /// 密钥 + /// + [Required(ErrorMessage = "密钥不能为空")] + public override string AccessSecret { get; set; } + + /// + /// 绑定用户Id + /// + [Required(ErrorMessage = "绑定用户不能为空")] + public override long BindUserId { get; set; } +} + +public class UpdateOpenAccessInput : AddOpenAccessInput +{ +} + +public class DeleteOpenAccessInput : BaseIdInput +{ +} + +public class GenerateSignatureInput +{ + /// + /// 身份标识 + /// + [Required(ErrorMessage = "身份标识不能为空")] + public string AccessKey { get; set; } + + /// + /// 密钥 + /// + [Required(ErrorMessage = "密钥不能为空")] + public string AccessSecret { get; set; } + + /// + /// 请求方法 + /// + public HttpMethodEnum Method { get; set; } + + /// + /// 请求接口地址 + /// + [Required(ErrorMessage = "请求接口地址不能为空")] + public string Url { get; set; } + + /// + /// 时间戳 + /// + [Required(ErrorMessage = "时间戳不能为空")] + public long Timestamp { get; set; } + + /// + /// 随机数 + /// + [Required(ErrorMessage = "随机数不能为空")] + public string Nonce { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessOutput.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessOutput.cs new file mode 100644 index 00000000..47516fd8 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessOutput.cs @@ -0,0 +1,20 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class OpenAccessOutput : SysOpenAccess +{ + /// + /// 绑定用户账号 + /// + public string BindUserAccount { get; set; } + + /// + /// 绑定租户名称 + /// + public string BindTenantName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs new file mode 100644 index 00000000..b8fdf2c9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs @@ -0,0 +1,195 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Security.Claims; +using System.Security.Cryptography; + +namespace Admin.NET.Core.Service; + +/// +/// 开放接口身份服务 🧩 +/// +[ApiDescriptionSettings(Order = 244)] +public class SysOpenAccessService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysOpenAccessRep; + private readonly SysCacheService _sysCacheService; + + /// + /// 开放接口身份服务构造函数 + /// + public SysOpenAccessService(SqlSugarRepository sysOpenAccessRep, + SysCacheService sysCacheService) + { + _sysOpenAccessRep = sysOpenAccessRep; + _sysCacheService = sysCacheService; + } + + /// + /// 生成签名 + /// + /// + /// + [DisplayName("生成签名")] + public string GenerateSignature(GenerateSignatureInput input) + { + // 密钥 + var appSecretByte = Encoding.UTF8.GetBytes(input.AccessSecret); + + // 拼接参数 + var parameter = $"{input.Method.ToString().ToUpper()}&{input.Url}&{input.AccessKey}&{input.Timestamp}&{input.Nonce}"; + // 使用 HMAC-SHA256 协议创建基于哈希的消息身份验证代码 (HMAC),以appSecretByte 作为密钥,对上面拼接的参数进行计算签名,所得签名进行 Base-64 编码 + using HMAC hmac = new HMACSHA256(); + hmac.Key = appSecretByte; + var sign = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(parameter))); + return sign; + } + + /// + /// 获取开放接口身份分页列表 🔖 + /// + /// + /// + [DisplayName("获取开放接口身份分页列表")] + public async Task> Page(OpenAccessInput input) + { + return await _sysOpenAccessRep.AsQueryable() + .LeftJoin((u, a) => u.BindUserId == a.Id) + .LeftJoin((u, a, b) => u.BindTenantId == b.Id) + .LeftJoin((u, a, b, c) => b.OrgId == c.Id) + .WhereIF(!string.IsNullOrWhiteSpace(input.AccessKey?.Trim()), (u, a, b, c) => u.AccessKey.Contains(input.AccessKey)) + .Select((u, a, b, c) => new OpenAccessOutput + { + BindUserAccount = a.Account, + BindTenantName = c.Name, + }, true) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加开放接口身份 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加开放接口身份")] + public async Task AddOpenAccess(AddOpenAccessInput input) + { + if (await _sysOpenAccessRep.AsQueryable().AnyAsync(u => u.AccessKey == input.AccessKey && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.O1000); + + var openAccess = input.Adapt(); + await _sysOpenAccessRep.InsertAsync(openAccess); + } + + /// + /// 更新开放接口身份 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新开放接口身份")] + public async Task UpdateOpenAccess(UpdateOpenAccessInput input) + { + if (await _sysOpenAccessRep.AsQueryable().AnyAsync(u => u.AccessKey == input.AccessKey && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.O1000); + + var openAccess = input.Adapt(); + _sysCacheService.Remove(CacheConst.KeyOpenAccess + openAccess.AccessKey); + + await _sysOpenAccessRep.UpdateAsync(openAccess); + } + + /// + /// 删除开放接口身份 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除开放接口身份")] + public async Task DeleteOpenAccess(DeleteOpenAccessInput input) + { + var openAccess = await _sysOpenAccessRep.GetFirstAsync(u => u.Id == input.Id); + if (openAccess != null) + _sysCacheService.Remove(CacheConst.KeyOpenAccess + openAccess.AccessKey); + + await _sysOpenAccessRep.DeleteAsync(u => u.Id == input.Id); + } + + /// + /// 创建密钥 🔖 + /// + /// + [DisplayName("创建密钥")] + public async Task CreateSecret() + { + return await Task.FromResult(Convert.ToBase64String(Guid.NewGuid().ToByteArray())[..^2]); + } + + /// + /// 根据 Key 获取对象 + /// + /// + /// + [NonAction] + public async Task GetByKey(string accessKey) + { + return await Task.FromResult( + _sysCacheService.GetOrAdd(CacheConst.KeyOpenAccess + accessKey, _ => + { + return _sysOpenAccessRep.AsQueryable() + .Includes(u => u.BindUser) + .Includes(u => u.BindUser, p => p.SysOrg) + .First(u => u.AccessKey == accessKey); + }) + ); + } + + /// + /// Signature 身份验证事件默认实现 + /// + [NonAction] + public static SignatureAuthenticationEvent GetSignatureAuthenticationEventImpl() + { + return new SignatureAuthenticationEvent + { + OnGetAccessSecret = context => + { + var logger = context.HttpContext.RequestServices.GetRequiredService>(); + try + { + var openAccessService = context.HttpContext.RequestServices.GetRequiredService(); + var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult(); + return Task.FromResult(openAccess == null ? "" : openAccess.AccessSecret); + } + catch (Exception ex) + { + logger.LogError(ex, "开放接口身份验证"); + return Task.FromResult(""); + } + }, + OnValidated = context => + { + var openAccessService = context.HttpContext.RequestServices.GetRequiredService(); + var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult(); + var identity = ((ClaimsIdentity)context.Principal!.Identity!); + + identity.AddClaims(new[] + { + new Claim(ClaimConst.UserId, openAccess.BindUserId + ""), + new Claim(ClaimConst.TenantId, openAccess.BindTenantId + ""), + new Claim(ClaimConst.Account, openAccess.BindUser.Account + ""), + new Claim(ClaimConst.RealName, openAccess.BindUser.RealName), + new Claim(ClaimConst.AccountType, ((int)openAccess.BindUser.AccountType).ToString()), + new Claim(ClaimConst.OrgId, openAccess.BindUser.OrgId + ""), + new Claim(ClaimConst.OrgName, openAccess.BindUser.SysOrg?.Name + ""), + new Claim(ClaimConst.OrgType, openAccess.BindUser.SysOrg?.Type + ""), + }); + return Task.CompletedTask; + } + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Org/Dto/OrgInput.cs b/Admin.NET/Admin.NET.Core/Service/Org/Dto/OrgInput.cs new file mode 100644 index 00000000..779d9b1b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Org/Dto/OrgInput.cs @@ -0,0 +1,42 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class OrgInput : BaseIdInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } + + /// + /// 机构类型 + /// + public string Type { get; set; } +} + +public class AddOrgInput : SysOrg +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "机构名称不能为空")] + public override string Name { get; set; } +} + +public class UpdateOrgInput : AddOrgInput +{ +} + +public class DeleteOrgInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Org/SysOrgService.cs b/Admin.NET/Admin.NET.Core/Service/Org/SysOrgService.cs new file mode 100644 index 00000000..d9b544b4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Org/SysOrgService.cs @@ -0,0 +1,425 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统机构服务 🧩 +/// +[ApiDescriptionSettings(Order = 470)] +public class SysOrgService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SysCacheService _sysCacheService; + private readonly SysUserExtOrgService _sysUserExtOrgService; + private readonly SysUserRoleService _sysUserRoleService; + private readonly SysRoleOrgService _sysRoleOrgService; + private readonly SqlSugarRepository _sysOrgRep; + + public SysOrgService(UserManager userManager, + SysCacheService sysCacheService, + SysUserExtOrgService sysUserExtOrgService, + SysUserRoleService sysUserRoleService, + SysRoleOrgService sysRoleOrgService, + SqlSugarRepository sysOrgRep) + { + _userManager = userManager; + _sysCacheService = sysCacheService; + _sysUserExtOrgService = sysUserExtOrgService; + _sysUserRoleService = sysUserRoleService; + _sysRoleOrgService = sysRoleOrgService; + _sysOrgRep = sysOrgRep; + } + + /// + /// 获取机构列表 🔖 + /// + /// + [DisplayName("获取机构列表")] + public async Task> GetList([FromQuery] OrgInput input) + { + // 获取拥有的机构Id集合 + var userOrgIdList = await GetUserOrgIdList(); + + var iSugarQueryable = _sysOrgRep.AsQueryable().OrderBy(u => u.OrderNo); + + // 带条件筛选时返回列表数据 + if (!string.IsNullOrWhiteSpace(input.Name) || !string.IsNullOrWhiteSpace(input.Code) || !string.IsNullOrWhiteSpace(input.Type)) + { + return await iSugarQueryable.WhereIF(userOrgIdList.Count > 0, u => userOrgIdList.Contains(u.Id)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code == input.Code) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), u => u.Type == input.Type) + .ToListAsync(); + } + + var orgTree = new List(); + if (_userManager.SuperAdmin) + { + orgTree = await iSugarQueryable.ToTreeAsync(u => u.Children, u => u.Pid, input.Id); + } + else + { + orgTree = await iSugarQueryable.ToTreeAsync(u => u.Children, u => u.Pid, input.Id, userOrgIdList.Select(d => (object)d).ToArray()); + // 递归禁用没权限的机构(防止用户修改或创建无权的机构和用户) + HandlerOrgTree(orgTree, userOrgIdList); + } + + var sysOrg = await _sysOrgRep.GetSingleAsync(u => u.Id == input.Id); + if (sysOrg != null) + { + sysOrg.Children = orgTree; + orgTree = new List { sysOrg }; + } + return orgTree; + } + + /// + /// 递归禁用没权限的机构 + /// + /// + /// + private static void HandlerOrgTree(List orgTree, List userOrgIdList) + { + foreach (var org in orgTree) + { + org.Disabled = !userOrgIdList.Contains(org.Id); // 设置禁用/不可选择 + if (org.Children != null) + HandlerOrgTree(org.Children, userOrgIdList); + } + } + + /// + /// 增加机构 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加机构")] + public async Task AddOrg(AddOrgInput input) + { + if (!_userManager.SuperAdmin && input.Pid == 0) + throw Oops.Oh(ErrorCodeEnum.D2009); + + if (await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code)) + throw Oops.Oh(ErrorCodeEnum.D2002); + + if (!_userManager.SuperAdmin && input.Pid != 0) + { + // 新增机构父Id不是0,则进行权限校验 + var orgIdList = await GetUserOrgIdList(); + // 新增机构的父机构不在自己的数据范围内 + if (orgIdList.Count < 1 || !orgIdList.Contains(input.Pid)) + throw Oops.Oh(ErrorCodeEnum.D2003); + } + + // 删除与此父机构有关的用户机构缓存 + if (input.Pid == 0) + { + DeleteAllUserOrgCache(0, 0); + } + else + { + var pOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Pid); + if (pOrg != null) + DeleteAllUserOrgCache(pOrg.Id, pOrg.Pid); + } + + var newOrg = await _sysOrgRep.AsInsertable(input.Adapt()).ExecuteReturnEntityAsync(); + return newOrg.Id; + } + + /// + /// 批量增加机构 + /// + /// + /// + [NonAction] + public async Task BatchAddOrgs(List orgs) + { + DeleteAllUserOrgCache(0, 0); + await _sysOrgRep.AsDeleteable().ExecuteCommandAsync(); + await _sysOrgRep.AsInsertable(orgs).ExecuteCommandAsync(); + } + + /// + /// 更新机构 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新机构")] + public async Task UpdateOrg(UpdateOrgInput input) + { + if (!_userManager.SuperAdmin && input.Pid == 0) + throw Oops.Oh(ErrorCodeEnum.D2009); + + if (input.Pid != 0) + { + //var pOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Pid); + //_ = pOrg ?? throw Oops.Oh(ErrorCodeEnum.D2000); + + // 若父机构发生变化则清空用户机构缓存 + var sysOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Id); + if (sysOrg != null && sysOrg.Pid != input.Pid) + { + // 删除与此机构、新父机构有关的用户机构缓存 + DeleteAllUserOrgCache(sysOrg.Id, input.Pid); + } + } + if (input.Id == input.Pid) + throw Oops.Oh(ErrorCodeEnum.D2001); + + if (await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.D2002); + + // 父Id不能为自己的子节点 + var childIdList = await GetChildIdListWithSelfById(input.Id); + if (childIdList.Contains(input.Pid)) + throw Oops.Oh(ErrorCodeEnum.D2001); + + // 是否有权限操作此机构 + if (!_userManager.SuperAdmin) + { + var orgIdList = await GetUserOrgIdList(); + if (orgIdList.Count < 1 || !orgIdList.Contains(input.Id)) + throw Oops.Oh(ErrorCodeEnum.D2003); + } + + await _sysOrgRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除机构 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除机构")] + public async Task DeleteOrg(DeleteOrgInput input) + { + var sysOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + + // 是否有权限操作此机构 + if (!_userManager.SuperAdmin) + { + var orgIdList = await GetUserOrgIdList(); + if (orgIdList.Count < 1 || !orgIdList.Contains(sysOrg.Id)) + throw Oops.Oh(ErrorCodeEnum.D2003); + } + + // 若机构为租户默认机构禁止删除 + var isTenantOrg = await _sysOrgRep.ChangeRepository>() + .IsAnyAsync(u => u.OrgId == input.Id); + if (isTenantOrg) + throw Oops.Oh(ErrorCodeEnum.D2008); + + // 若机构有用户则禁止删除 + var orgHasEmp = await _sysOrgRep.ChangeRepository>() + .IsAnyAsync(u => u.OrgId == input.Id); + if (orgHasEmp) + throw Oops.Oh(ErrorCodeEnum.D2004); + + // 若扩展机构有用户则禁止删除 + var hasExtOrgEmp = await _sysUserExtOrgService.HasUserOrg(sysOrg.Id); + if (hasExtOrgEmp) + throw Oops.Oh(ErrorCodeEnum.D2005); + + // 若子机构有用户则禁止删除 + var childOrgTreeList = await _sysOrgRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); + var childOrgIdList = childOrgTreeList.Select(u => u.Id).ToList(); + + // 若子机构有用户则禁止删除 + var cOrgHasEmp = await _sysOrgRep.ChangeRepository>() + .IsAnyAsync(u => childOrgIdList.Contains(u.OrgId)); + if (cOrgHasEmp) + throw Oops.Oh(ErrorCodeEnum.D2007); + + // 删除与此机构、父机构有关的用户机构缓存 + DeleteAllUserOrgCache(sysOrg.Id, sysOrg.Pid); + + // 级联删除机构子节点 + await _sysOrgRep.DeleteAsync(u => childOrgIdList.Contains(u.Id)); + + // 级联删除角色机构数据 + await _sysRoleOrgService.DeleteRoleOrgByOrgIdList(childOrgIdList); + + // 级联删除用户机构数据 + await _sysUserExtOrgService.DeleteUserExtOrgByOrgIdList(childOrgIdList); + } + + /// + /// 删除与此机构、父机构有关的用户机构缓存 + /// + /// + /// + private void DeleteAllUserOrgCache(long orgId, long orgPid) + { + var userOrgKeyList = _sysCacheService.GetKeysByPrefixKey(CacheConst.KeyUserOrg); + if (userOrgKeyList != null && userOrgKeyList.Count > 0) + { + foreach (var userOrgKey in userOrgKeyList) + { + var userOrgs = _sysCacheService.Get>(userOrgKey); + var userId = long.Parse(userOrgKey.Substring(CacheConst.KeyUserOrg)); + if (userOrgs != null && (userOrgs.Contains(orgId) || userOrgs.Contains(orgPid))) + { + SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString()); + } + if (orgPid == 0) + { + var dataScope = _sysCacheService.Get($"{CacheConst.KeyRoleMaxDataScope}{userId}"); + if (dataScope == (int)DataScopeEnum.All) + { + SqlSugarFilter.DeleteUserOrgCache(userId, _sysOrgRep.Context.CurrentConnectionConfig.ConfigId.ToString()); + } + } + } + } + } + + /// + /// 获取当前用户机构Id集合 + /// + /// + [NonAction] + public async Task> GetUserOrgIdList() + { + if (_userManager.SuperAdmin) + return new List(); + return await GetUserOrgIdList(_userManager.UserId, _userManager.OrgId); + } + + /// + /// 根据指定用户Id获取机构Id集合 + /// + /// + [NonAction] + public async Task> GetUserOrgIdList(long userId, long userOrgId) + { + var orgIdList = _sysCacheService.Get>($"{CacheConst.KeyUserOrg}{userId}"); // 取缓存 + if (orgIdList == null || orgIdList.Count < 1) + { + // 本人创建机构集合 + var orgList0 = await _sysOrgRep.AsQueryable().Where(u => u.CreateUserId == userId).Select(u => u.Id).ToListAsync(); + // 扩展机构集合 + var orgList1 = await _sysUserExtOrgService.GetUserExtOrgList(userId); + // 角色机构集合 + var orgList2 = await GetUserRoleOrgIdList(userId, userOrgId); + // 机构并集 + orgIdList = orgList1.Select(u => u.OrgId).Union(orgList2).Union(orgList0).ToList(); + // 当前所属机构 + if (!orgIdList.Contains(userOrgId)) + orgIdList.Add(userOrgId); + _sysCacheService.Set($"{CacheConst.KeyUserOrg}{userId}", orgIdList, TimeSpan.FromDays(7)); // 存缓存 + } + return orgIdList; + } + + /// + /// 获取用户角色机构Id集合 + /// + /// + /// 用户的机构Id + /// + private async Task> GetUserRoleOrgIdList(long userId, long userOrgId) + { + var roleList = await _sysUserRoleService.GetUserRoleList(userId); + if (roleList.Count < 1) + return new List(); // 空机构Id集合 + + return await GetUserOrgIdList(roleList, userId, userOrgId); + } + + /// + /// 根据角色Id集合获取机构Id集合 + /// + /// + /// + /// 用户的机构Id + /// + private async Task> GetUserOrgIdList(List roleList, long userId, long userOrgId) + { + // 按最大范围策略设定(若同时拥有ALL和SELF权限,则结果ALL) + int strongerDataScopeType = (int)DataScopeEnum.Self; + + // 自定义数据范围的角色集合 + var customDataScopeRoleIdList = new List(); + + // 数据范围的机构集合 + var dataScopeOrgIdList = new List(); + + if (roleList != null && roleList.Count > 0) + { + roleList.ForEach(u => + { + if (u.DataScope == DataScopeEnum.Define) + { + customDataScopeRoleIdList.Add(u.Id); + strongerDataScopeType = (int)u.DataScope; // 自定义数据权限时也要更新最大范围 + } + else if ((int)u.DataScope <= strongerDataScopeType) + { + strongerDataScopeType = (int)u.DataScope; + // 根据数据范围获取机构集合 + var orgIds = GetOrgIdListByDataScope(userOrgId, strongerDataScopeType).GetAwaiter().GetResult(); + dataScopeOrgIdList = dataScopeOrgIdList.Union(orgIds).ToList(); + } + }); + } + + // 缓存当前用户最大角色数据范围 + _sysCacheService.Set(CacheConst.KeyRoleMaxDataScope + userId, strongerDataScopeType, TimeSpan.FromDays(7)); + + // 根据角色集合获取机构集合 + var roleOrgIdList = await _sysRoleOrgService.GetRoleOrgIdList(customDataScopeRoleIdList); + + // 并集机构集合 + return roleOrgIdList.Union(dataScopeOrgIdList).ToList(); + } + + /// + /// 根据数据范围获取机构Id集合 + /// + /// 用户的机构Id + /// + /// + private async Task> GetOrgIdListByDataScope(long userOrgId, int dataScope) + { + var orgId = userOrgId;//var orgId = _userManager.OrgId; + var orgIdList = new List(); + // 若数据范围是全部,则获取所有机构Id集合 + if (dataScope == (int)DataScopeEnum.All) + { + orgIdList = await _sysOrgRep.AsQueryable().Select(u => u.Id).ToListAsync(); + } + // 若数据范围是本部门及以下,则获取本节点和子节点集合 + else if (dataScope == (int)DataScopeEnum.DeptChild) + { + orgIdList = await GetChildIdListWithSelfById(orgId); + } + // 若数据范围是本部门不含子节点,则直接返回本部门 + else if (dataScope == (int)DataScopeEnum.Dept) + { + orgIdList.Add(orgId); + } + return orgIdList; + } + + /// + /// 根据节点Id获取子节点Id集合(包含自己) + /// + /// + /// + [NonAction] + public async Task> GetChildIdListWithSelfById(long pid) + { + var orgTreeList = await _sysOrgRep.AsQueryable().ToChildListAsync(u => u.Pid, pid, true); + return orgTreeList.Select(u => u.Id).ToList(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Plugin/Dto/PluginInput.cs b/Admin.NET/Admin.NET.Core/Service/Plugin/Dto/PluginInput.cs new file mode 100644 index 00000000..f0773543 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Plugin/Dto/PluginInput.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PagePluginInput : BasePageInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class AddPluginInput : SysPlugin +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "功能名称不能为空")] + public override string Name { get; set; } +} + +public class UpdatePluginInput : AddPluginInput +{ +} + +public class DeletePluginInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Plugin/SysPluginService.cs b/Admin.NET/Admin.NET.Core/Service/Plugin/SysPluginService.cs new file mode 100644 index 00000000..24b5e8bf --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Plugin/SysPluginService.cs @@ -0,0 +1,124 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统动态插件服务 🧩 +/// +[ApiDescriptionSettings(Order = 245)] +public class SysPluginService : IDynamicApiController, ITransient +{ + private readonly IDynamicApiRuntimeChangeProvider _provider; + private readonly SqlSugarRepository _sysPluginRep; + + public SysPluginService(IDynamicApiRuntimeChangeProvider provider, + SqlSugarRepository sysPluginRep) + { + _provider = provider; + _sysPluginRep = sysPluginRep; + } + + /// + /// 获取动态插件列表 🧩 + /// + /// + /// + [DisplayName("获取动态插件列表")] + public async Task> Page(PagePluginInput input) + { + return await _sysPluginRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .OrderBy(u => u.OrderNo) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加动态插件 🧩 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加动态插件")] + public async Task AddPlugin(AddPluginInput input) + { + var isExist = await _sysPluginRep.IsAnyAsync(u => u.Name == input.Name || u.AssemblyName == input.AssemblyName); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1900); + + // 添加动态程序集/接口 + input.AssemblyName = CompileAssembly(input.CsharpCode, input.AssemblyName); + + await _sysPluginRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新动态插件 🧩 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新动态插件")] + public async Task UpdatePlugin(UpdatePluginInput input) + { + var isExist = await _sysPluginRep.IsAnyAsync(u => (u.Name == input.Name || u.AssemblyName == input.AssemblyName) && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1900); + + // 先移除再添加动态程序集/接口 + RemoveAssembly(input.AssemblyName); + input.AssemblyName = CompileAssembly(input.CsharpCode); + + await _sysPluginRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除动态插件 🧩 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除动态插件")] + public async Task DeletePlugin(DeletePluginInput input) + { + var plugin = await _sysPluginRep.GetByIdAsync(input.Id); + if (plugin == null) return; + + // 移除动态程序集/接口 + RemoveAssembly(plugin.AssemblyName); + + await _sysPluginRep.DeleteAsync(u => u.Id == input.Id); + } + + /// + /// 添加动态程序集/接口 🧩 + /// + /// + /// 程序集名称 + /// + [DisplayName("添加动态程序集/接口")] + public string CompileAssembly([FromBody] string csharpCode, [FromQuery] string assemblyName = default) + { + // 编译 C# 代码并返回动态程序集 + var dynamicAssembly = App.CompileCSharpClassCode(csharpCode, assemblyName); + + // 将程序集添加进动态 WebAPI 应用部件 + _provider.AddAssembliesWithNotifyChanges(dynamicAssembly); + + // 返回动态程序集名称 + return dynamicAssembly.GetName().Name; + } + + /// + /// 移除动态程序集/接口 🧩 + /// + [ApiDescriptionSettings(Name = "RemoveAssembly"), HttpPost] + [DisplayName("移除动态程序集/接口")] + public void RemoveAssembly(string assemblyName) + { + _provider.RemoveAssembliesWithNotifyChanges(assemblyName); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosInput.cs b/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosInput.cs new file mode 100644 index 00000000..183666f5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosInput.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PosInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class AddPosInput : SysPos +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "职位名称不能为空")] + public override string Name { get; set; } +} + +public class UpdatePosInput : AddPosInput +{ +} + +public class DeletePosInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosOutput.cs b/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosOutput.cs new file mode 100644 index 00000000..94e673c2 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Pos/Dto/PosOutput.cs @@ -0,0 +1,15 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PosOutput : SysPos +{ + /// + /// 租户名称 + /// + public string TenantName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Pos/SysPosService.cs b/Admin.NET/Admin.NET.Core/Service/Pos/SysPosService.cs new file mode 100644 index 00000000..64360041 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Pos/SysPosService.cs @@ -0,0 +1,108 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统职位服务 🧩 +/// +[ApiDescriptionSettings(Order = 460)] +public class SysPosService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysPosRep; + private readonly SysUserExtOrgService _sysUserExtOrgService; + + public SysPosService(UserManager userManager, + SqlSugarRepository sysPosRep, + SysUserExtOrgService sysUserExtOrgService) + { + _userManager = userManager; + _sysPosRep = sysPosRep; + _sysUserExtOrgService = sysUserExtOrgService; + } + + /// + /// 获取职位列表 🔖 + /// + /// + /// + [DisplayName("获取职位列表")] + public async Task> GetList([FromQuery] PosInput input) + { + return await _sysPosRep.AsQueryable() + .LeftJoin((u, a) => u.TenantId == a.Id) + .LeftJoin((u, a, b) => a.OrgId == b.Id) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code)) + .Select((u, a, b) => new PosOutput + { + TenantName = b.Name + }, true) + .OrderBy(u => u.OrderNo).ToListAsync(); + } + + /// + /// 增加职位 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加职位")] + public async Task AddPos(AddPosInput input) + { + if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code)) + throw Oops.Oh(ErrorCodeEnum.D6000); + + await _sysPosRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新职位 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新职位")] + public async Task UpdatePos(UpdatePosInput input) + { + if (await _sysPosRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.D6000); + + var sysPos = await _sysPosRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D6003); + if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId) + throw Oops.Oh(ErrorCodeEnum.D6002); + + await _sysPosRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除职位 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除职位")] + public async Task DeletePos(DeletePosInput input) + { + var sysPos = await _sysPosRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D6003); + if (!_userManager.SuperAdmin && sysPos.CreateUserId != _userManager.UserId) + throw Oops.Oh(ErrorCodeEnum.D6002); + + // 若职位有用户则禁止删除 + var hasPosEmp = await _sysPosRep.ChangeRepository>() + .IsAnyAsync(u => u.PosId == input.Id); + if (hasPosEmp) + throw Oops.Oh(ErrorCodeEnum.D6001); + + // 若附属职位有用户则禁止删除 + var hasExtPosEmp = await _sysUserExtOrgService.HasUserPos(input.Id); + if (hasExtPosEmp) + throw Oops.Oh(ErrorCodeEnum.D6001); + + await _sysPosRep.DeleteAsync(u => u.Id == input.Id); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Print/Dto/PrintInput.cs b/Admin.NET/Admin.NET.Core/Service/Print/Dto/PrintInput.cs new file mode 100644 index 00000000..48887061 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Print/Dto/PrintInput.cs @@ -0,0 +1,37 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PagePrintInput : BasePageInput +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class AddPrintInput : SysPrint +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "模板名称不能为空")] + public override string Name { get; set; } +} + +public class UpdatePrintInput : AddPrintInput +{ +} + +public class DeletePrintInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Print/SysPrintService.cs b/Admin.NET/Admin.NET.Core/Service/Print/SysPrintService.cs new file mode 100644 index 00000000..23c2a4e1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Print/SysPrintService.cs @@ -0,0 +1,90 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统打印模板服务 🧩 +/// +[ApiDescriptionSettings(Order = 305)] +public class SysPrintService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysPrintRep; + + public SysPrintService(SqlSugarRepository sysPrintRep) + { + _sysPrintRep = sysPrintRep; + } + + /// + /// 获取打印模板列表 🖨️ + /// + /// + /// + [DisplayName("获取打印模板列表")] + public async Task> Page(PagePrintInput input) + { + return await _sysPrintRep.AsQueryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .OrderBy(u => u.OrderNo) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取打印模板 🖨️ + /// + /// + /// + [DisplayName("获取打印模板")] + public async Task GetPrint(string name) + { + return await _sysPrintRep.GetFirstAsync(u => u.Name == name); + } + + /// + /// 增加打印模板 🖨️ + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加打印模板")] + public async Task AddPrint(AddPrintInput input) + { + var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1800); + + await _sysPrintRep.InsertAsync(input.Adapt()); + } + + /// + /// 更新打印模板 🖨️ + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新打印模板")] + public async Task UpdatePrint(UpdatePrintInput input) + { + var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1800); + + await _sysPrintRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除打印模板 🖨️ + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除打印模板")] + public async Task DeletePrint(DeletePrintInput input) + { + await _sysPrintRep.DeleteAsync(u => u.Id == input.Id); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Region/Dto/RegionInput.cs b/Admin.NET/Admin.NET.Core/Service/Region/Dto/RegionInput.cs new file mode 100644 index 00000000..bb465613 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Region/Dto/RegionInput.cs @@ -0,0 +1,46 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class PageRegionInput : BasePageInput +{ + /// + /// 父节点Id + /// + public long Pid { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +public class RegionInput : BaseIdInput +{ +} + +public class AddRegionInput : SysRegion +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "名称不能为空")] + public override string Name { get; set; } +} + +public class UpdateRegionInput : AddRegionInput +{ +} + +public class DeleteRegionInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs new file mode 100644 index 00000000..bf0ca92d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs @@ -0,0 +1,236 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统行政区域服务 🧩 +/// +[ApiDescriptionSettings(Order = 310)] +public class SysRegionService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysRegionRep; + + //// Url地址-国家统计局行政区域2023年 + //private readonly string _url = "http://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/2023/index.html"; + + public SysRegionService(SqlSugarRepository sysRegionRep) + { + _sysRegionRep = sysRegionRep; + } + + /// + /// 获取行政区域分页列表 🔖 + /// + /// + /// + [DisplayName("获取行政区域分页列表")] + public async Task> Page(PageRegionInput input) + { + return await _sysRegionRep.AsQueryable() + .WhereIF(input.Pid > 0, u => u.Pid == input.Pid || u.Id == input.Pid) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code)) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取行政区域列表 🔖 + /// + /// + /// + [DisplayName("获取行政区域列表")] + public async Task> GetList([FromQuery] RegionInput input) + { + return await _sysRegionRep.GetListAsync(u => u.Pid == input.Id); + } + + /// + /// 增加行政区域 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加行政区域")] + public async Task AddRegion(AddRegionInput input) + { + input.Code = input.Code.Trim(); + if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) + throw Oops.Oh("行政区代码只能为6、9或12位"); + + if (input.Pid != 0) + { + var pRegion = await _sysRegionRep.GetFirstAsync(u => u.Id == input.Pid); + pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString()); + if (pRegion == null) + throw Oops.Oh(ErrorCodeEnum.D2000); + input.Pid = pRegion.Id; + } + + var isExist = await _sysRegionRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.R2002); + + var sysRegion = input.Adapt(); + var newRegion = await _sysRegionRep.AsInsertable(sysRegion).ExecuteReturnEntityAsync(); + return newRegion.Id; + } + + /// + /// 更新行政区域 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新行政区域")] + public async Task UpdateRegion(UpdateRegionInput input) + { + input.Code = input.Code.Trim(); + if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) + throw Oops.Oh("行政区代码只能为6、9或12位"); + + if (input.Pid != input.Pid && input.Pid != 0) + { + var pRegion = await _sysRegionRep.GetFirstAsync(u => u.Id == input.Pid); + pRegion ??= await _sysRegionRep.GetFirstAsync(u => u.Code == input.Pid.ToString()); + if (pRegion == null) + throw Oops.Oh(ErrorCodeEnum.D2000); + + input.Pid = pRegion.Id; + var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); + var childIdList = regionTreeList.Select(u => u.Id).ToList(); + if (childIdList.Contains(input.Pid)) + throw Oops.Oh("父节点不能为自己的子节点"); + } + + if (input.Id == input.Pid) + throw Oops.Oh(ErrorCodeEnum.R2001); + + var sysRegion = await _sysRegionRep.GetFirstAsync(u => u.Id == input.Id); + var isExist = await _sysRegionRep.IsAnyAsync(u => (u.Name == input.Name && u.Code == input.Code) && u.Id != sysRegion.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.R2002); + + //// 父Id不能为自己的子节点 + //var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); + //var childIdList = regionTreeList.Select(u => u.Id).ToList(); + //if (childIdList.Contains(input.Pid)) + // throw Oops.Oh(ErrorCodeEnum.R2001); + + await _sysRegionRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + } + + /// + /// 删除行政区域 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除行政区域")] + public async Task DeleteRegion(DeleteRegionInput input) + { + var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); + var regionIdList = regionTreeList.Select(u => u.Id).ToList(); + await _sysRegionRep.DeleteAsync(u => regionIdList.Contains(u.Id)); + } + + /// + /// 同步行政区域 🔖 + /// + /// + [DisplayName("同步行政区域")] + public async Task Sync() + { + await _sysRegionRep.DeleteAsync(u => u.Id > 0); + + //var context = BrowsingContext.New(AngleSharp.Configuration.Default.WithDefaultLoader()); + //var dom = await context.OpenAsync(_url); + + //// 省级 + //var itemList = dom.QuerySelectorAll("table.provincetable tr.provincetr td a"); + //foreach (IHtmlAnchorElement item in itemList) + //{ + // var region = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion + // { + // Pid = 0, + // Name = item.TextContent, + // Remark = item.Href, + // Level = 1, + // }); + + // // 市级 + // if (string.IsNullOrEmpty(item.Href)) + // continue; + // var dom1 = await context.OpenAsync(item.Href); + // var itemList1 = dom1.QuerySelectorAll("table.citytable tr.citytr td a"); + // for (var i1 = 0; i1 < itemList1.Length; i1 += 2) + // { + // var item1 = (IHtmlAnchorElement)itemList1[i1 + 1]; + // var region1 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion + // { + // Pid = region.Id, + // Name = item1.TextContent, + // Code = itemList1[i1].TextContent, + // Remark = item1.Href, + // Level = 2, + // }); + + // // 区县级 + // if (string.IsNullOrEmpty(item1.Href)) + // continue; + // var dom2 = await context.OpenAsync(item1.Href); + // var itemList2 = dom2.QuerySelectorAll("table.countytable tr.countytr td a"); + // for (var i2 = 0; i2 < itemList2.Length; i2 += 2) + // { + // var item2 = (IHtmlAnchorElement)itemList2[i2 + 1]; + // var region2 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion + // { + // Pid = region1.Id, + // Name = item2.TextContent, + // Code = itemList2[i2].TextContent, + // Remark = item2.Href, + // Level = 3, + // }); + + // // 街道级 + // if (string.IsNullOrEmpty(item2.Href)) + // continue; + // var dom3 = await context.OpenAsync(item2.Href); + // var itemList3 = dom3.QuerySelectorAll("table.towntable tr.towntr td a"); + // for (var i3 = 0; i3 < itemList3.Length; i3 += 2) + // { + // var item3 = (IHtmlAnchorElement)itemList3[i3 + 1]; + // var region3 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion + // { + // Pid = region2.Id, + // Name = item3.TextContent, + // Code = itemList3[i3].TextContent, + // Remark = item3.Href, + // Level = 4, + // }); + + // // 村级 + // if (string.IsNullOrEmpty(item3.Href)) + // continue; + // var dom4 = await context.OpenAsync(item3.Href); + // var itemList4 = dom4.QuerySelectorAll("table.villagetable tr.villagetr td"); + // for (var i4 = 0; i4 < itemList4.Length; i4 += 3) + // { + // await _sysRegionRep.InsertAsync(new SysRegion + // { + // Pid = region3.Id, + // Name = itemList4[i4 + 2].TextContent, + // Code = itemList4[i4].TextContent, + // CityCode = itemList4[i4 + 1].TextContent, + // Level = 5, + // }); + // } + // } + // } + // } + //} + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleApiInput.cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleApiInput.cs new file mode 100644 index 00000000..d9824327 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleApiInput.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 授权角色接口资源 +/// +public class RoleApiInput : BaseIdInput +{ + /// + /// 接口路由集合 + /// + public List ApiList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleInput.cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleInput.cs new file mode 100644 index 00000000..f6f9370a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleInput.cs @@ -0,0 +1,50 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class RoleInput : BaseIdInput +{ + /// + /// 状态 + /// + public virtual StatusEnum Status { get; set; } +} + +public class PageRoleInput : BasePageInput +{ + /// + /// 名称 + /// + public virtual string Name { get; set; } + + /// + /// 编码 + /// + public virtual string Code { get; set; } +} + +public class AddRoleInput : SysRole +{ + /// + /// 名称 + /// + [Required(ErrorMessage = "角色名称不能为空")] + public override string Name { get; set; } + + /// + /// 菜单Id集合 + /// + public List MenuIdList { get; set; } +} + +public class UpdateRoleInput : AddRoleInput +{ +} + +public class DeleteRoleInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuInput.cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuInput.cs new file mode 100644 index 00000000..95712234 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuInput.cs @@ -0,0 +1,18 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 授权角色菜单 +/// +public class RoleMenuInput : BaseIdInput +{ + /// + /// 菜单Id集合 + /// + public List MenuIdList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuOutput .cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuOutput .cs new file mode 100644 index 00000000..e2a45115 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuOutput .cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 角色菜单输出参数 +/// +public class RoleMenuOutput +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 名称 + /// + public string Title { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOrgInput.cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOrgInput.cs new file mode 100644 index 00000000..51e80105 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOrgInput.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 授权角色机构 +/// +public class RoleOrgInput : BaseIdInput +{ + /// + /// 数据范围 + /// + public int DataScope { get; set; } + + /// + /// 机构Id集合 + /// + public List OrgIdList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOutput.cs b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOutput.cs new file mode 100644 index 00000000..d52373f5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleOutput.cs @@ -0,0 +1,39 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 角色列表输出参数 +/// +public class RoleOutput +{ + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 编码 + /// + public string Code { get; set; } +} + +/// +/// 角色分页列表输出参数 +/// +public class PageRoleOutput : SysRole +{ + /// + /// 租户名称 + /// + public string TenantName { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs new file mode 100644 index 00000000..ddb6b680 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs @@ -0,0 +1,59 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统角色接口资源服务 +/// +public class SysRoleApiService : ITransient +{ + private readonly SqlSugarRepository _sysRoleApiRep; + + public SysRoleApiService(SqlSugarRepository sysRoleApi) + { + _sysRoleApiRep = sysRoleApi; + } + + /// + /// 授权角色接口资源 + /// + /// + /// + public async Task GrantRoleApi(RoleApiInput input) + { + await _sysRoleApiRep.DeleteAsync(u => u.RoleId == input.Id); + + var roleApis = input.ApiList.Select(u => new SysRoleApi + { + RoleId = input.Id, + Route = u + }).ToList(); + await _sysRoleApiRep.InsertRangeAsync(roleApis); + } + + /// + /// 根据角色Id集合获取接口资源集合 + /// + /// + /// + public async Task> GetRoleApiList(List roleIdList) + { + return await _sysRoleApiRep.AsQueryable() + .Where(u => roleIdList.Contains(u.RoleId)) + .Select(u => u.Route).ToListAsync(); + } + + /// + /// 根据角色Id删除接口资源 + /// + /// + /// + public async Task DeleteRoleApiByRoleId(long roleId) + { + await _sysRoleApiRep.DeleteAsync(u => u.RoleId == roleId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleMenuService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleMenuService.cs new file mode 100644 index 00000000..f8dce9bf --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleMenuService.cs @@ -0,0 +1,75 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统角色菜单服务 +/// +public class SysRoleMenuService : ITransient +{ + private readonly SqlSugarRepository _sysRoleMenuRep; + private readonly SysCacheService _sysCacheService; + + public SysRoleMenuService(SqlSugarRepository sysRoleMenuRep, + SysCacheService sysCacheService) + { + _sysRoleMenuRep = sysRoleMenuRep; + _sysCacheService = sysCacheService; + } + + /// + /// 根据角色Id集合获取菜单Id集合 + /// + /// + /// + public async Task> GetRoleMenuIdList(List roleIdList) + { + return await _sysRoleMenuRep.AsQueryable() + .Where(u => roleIdList.Contains(u.RoleId)) + .Select(u => u.MenuId).ToListAsync(); + } + + /// + /// 授权角色菜单 + /// + /// + /// + public async Task GrantRoleMenu(RoleMenuInput input) + { + await _sysRoleMenuRep.DeleteAsync(u => u.RoleId == input.Id); + var menus = input.MenuIdList.Select(u => new SysRoleMenu + { + RoleId = input.Id, + MenuId = u + }).ToList(); + await _sysRoleMenuRep.InsertRangeAsync(menus); + + // 清除缓存 + // _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserMenu); + _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserButton); + } + + /// + /// 根据菜单Id集合删除角色菜单 + /// + /// + /// + public async Task DeleteRoleMenuByMenuIdList(List menuIdList) + { + await _sysRoleMenuRep.DeleteAsync(u => menuIdList.Contains(u.MenuId)); + } + + /// + /// 根据角色Id删除角色菜单 + /// + /// + /// + public async Task DeleteRoleMenuByRoleId(long roleId) + { + await _sysRoleMenuRep.DeleteAsync(u => u.RoleId == roleId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleOrgService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleOrgService.cs new file mode 100644 index 00000000..a1ff53e5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleOrgService.cs @@ -0,0 +1,75 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统角色机构服务 +/// +public class SysRoleOrgService : ITransient +{ + private readonly SqlSugarRepository _sysRoleOrgRep; + + public SysRoleOrgService(SqlSugarRepository sysRoleOrgRep) + { + _sysRoleOrgRep = sysRoleOrgRep; + } + + /// + /// 授权角色机构 + /// + /// + /// + public async Task GrantRoleOrg(RoleOrgInput input) + { + await _sysRoleOrgRep.DeleteAsync(u => u.RoleId == input.Id); + if (input.DataScope == (int)DataScopeEnum.Define) + { + var roleOrgs = input.OrgIdList.Select(u => new SysRoleOrg + { + RoleId = input.Id, + OrgId = u + }).ToList(); + await _sysRoleOrgRep.InsertRangeAsync(roleOrgs); + } + } + + /// + /// 根据角色Id集合获取角色机构Id集合 + /// + /// + /// + public async Task> GetRoleOrgIdList(List roleIdList) + { + if (roleIdList?.Count > 0) + { + return await _sysRoleOrgRep.AsQueryable() + .Where(u => roleIdList.Contains(u.RoleId)) + .Select(u => u.OrgId).ToListAsync(); + } + else return new List(); + } + + /// + /// 根据机构Id集合删除角色机构 + /// + /// + /// + public async Task DeleteRoleOrgByOrgIdList(List orgIdList) + { + await _sysRoleOrgRep.DeleteAsync(u => orgIdList.Contains(u.OrgId)); + } + + /// + /// 根据角色Id删除角色机构 + /// + /// + /// + public async Task DeleteRoleOrgByRoleId(long roleId) + { + await _sysRoleOrgRep.DeleteAsync(u => u.RoleId == roleId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs new file mode 100644 index 00000000..64809217 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs @@ -0,0 +1,325 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统角色服务 🧩 +/// +[ApiDescriptionSettings(Order = 480)] +public class SysRoleService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysRoleRep; + private readonly SysRoleOrgService _sysRoleOrgService; + private readonly SysRoleMenuService _sysRoleMenuService; + private readonly SysRoleApiService _sysRoleApiService; + private readonly SysOrgService _sysOrgService; + private readonly SysUserRoleService _sysUserRoleService; + private readonly SysCacheService _sysCacheService; + + public SysRoleService(UserManager userManager, + SqlSugarRepository sysRoleRep, + SysRoleOrgService sysRoleOrgService, + SysRoleMenuService sysRoleMenuService, + SysRoleApiService sysRoleApiService, + SysOrgService sysOrgService, + SysUserRoleService sysUserRoleService, + SysCacheService sysCacheService) + { + _userManager = userManager; + _sysRoleRep = sysRoleRep; + _sysRoleOrgService = sysRoleOrgService; + _sysRoleMenuService = sysRoleMenuService; + _sysRoleApiService = sysRoleApiService; + _sysOrgService = sysOrgService; + _sysUserRoleService = sysUserRoleService; + _sysCacheService = sysCacheService; + } + + /// + /// 获取角色分页列表 🔖 + /// + /// + /// + [DisplayName("获取角色分页列表")] + public async Task> Page(PageRoleInput input) + { + return await _sysRoleRep.AsQueryable() + .LeftJoin((u, a) => u.TenantId == a.Id) + .LeftJoin((u, a, b) => a.OrgId == b.Id) + .WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色 + .WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId) // 若非超管且非系统管理员,则只能操作自己创建的角色 + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code)) + .Select((u, a, b) => new PageRoleOutput + { + TenantName = b.Name + }, true) + .OrderBy(u => u.OrderNo) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取角色列表 🔖 + /// + /// + [DisplayName("获取角色列表")] + public async Task> GetList() + { + // 当前用户已拥有的角色集合 + var roleIdList = _userManager.SuperAdmin ? new List() : await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId); + + return await _sysRoleRep.AsQueryable() + .WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色 + .WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId || roleIdList.Contains(u.Id)) // 若非超管且非系统管理员,则只显示自己创建和已拥有的角色 + .OrderBy(u => u.OrderNo).Select().ToListAsync(); + } + + /// + /// 增加角色 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加角色")] + public async Task AddRole(AddRoleInput input) + { + if (await _sysRoleRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code)) + throw Oops.Oh(ErrorCodeEnum.D1006); + + var newRole = await _sysRoleRep.AsInsertable(input.Adapt()).ExecuteReturnEntityAsync(); + input.Id = newRole.Id; + await UpdateRoleMenu(input); + + // 增加基础接口资源 + await _sysRoleApiService.GrantRoleApi(new RoleApiInput { Id = newRole.Id, ApiList = CommonConst.SysBaseRoutes }); + } + + /// + /// 更新角色菜单权限 + /// + /// + /// + private async Task UpdateRoleMenu(AddRoleInput input) + { + if (input.MenuIdList == null || input.MenuIdList.Count < 1) + return; + + // 将父节点为0的菜单排除,防止前端全选异常 + var pMenuIds = await _sysRoleRep.ChangeRepository>().AsQueryable().Where(u => input.MenuIdList.Contains(u.Id) && u.Pid == 0).ToListAsync(u => u.Id); + var menuIds = input.MenuIdList.Except(pMenuIds); // 差集 + await GrantMenu(new RoleMenuInput() + { + Id = input.Id, + MenuIdList = menuIds.ToList() + }); + } + + /// + /// 更新角色 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新角色")] + public async Task UpdateRole(UpdateRoleInput input) + { + if (await _sysRoleRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.D1006); + + await _sysRoleRep.AsUpdateable(input.Adapt()).IgnoreColumns(true) + .IgnoreColumns(u => new { u.DataScope }).ExecuteCommandAsync(); + + await UpdateRoleMenu(input); + } + + /// + /// 删除角色 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除角色")] + public async Task DeleteRole(DeleteRoleInput input) + { + // 禁止删除系统管理员角色 + var sysRole = await _sysRoleRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002); + if (sysRole.Code == CommonConst.SysAdminRole) + throw Oops.Oh(ErrorCodeEnum.D1019); + + // 若角色有用户则禁止删除 + var userIds = await _sysUserRoleService.GetUserIdList(input.Id); + if (userIds != null && userIds.Count > 0) + throw Oops.Oh(ErrorCodeEnum.D1025); + + await _sysRoleRep.DeleteAsync(sysRole); + + // 级联删除角色机构数据 + await _sysRoleOrgService.DeleteRoleOrgByRoleId(sysRole.Id); + + // 级联删除用户角色数据 + await _sysUserRoleService.DeleteUserRoleByRoleId(sysRole.Id); + + // 级联删除角色菜单数据 + await _sysRoleMenuService.DeleteRoleMenuByRoleId(sysRole.Id); + + // 级联删除角色接口数据 + await _sysRoleApiService.DeleteRoleApiByRoleId(sysRole.Id); + } + + /// + /// 授权角色菜单 🔖 + /// + /// + /// + [UnitOfWork] + [DisplayName("授权角色菜单")] + public async Task GrantMenu(RoleMenuInput input) + { + // 删除与该角色相关的用户菜单缓存 + var userIdList = await _sysUserRoleService.GetUserIdList(input.Id); + foreach (var userId in userIdList) + { + _sysCacheService.Remove(CacheConst.KeyUserButton + userId); + } + + await _sysRoleMenuService.GrantRoleMenu(input); + } + + /// + /// 授权角色数据范围 🔖 + /// + /// + /// + [UnitOfWork] + [DisplayName("授权角色数据范围")] + public async Task GrantDataScope(RoleOrgInput input) + { + // 删除与该角色相关的用户机构缓存 + var userIdList = await _sysUserRoleService.GetUserIdList(input.Id); + foreach (var userId in userIdList) + { + SqlSugarFilter.DeleteUserOrgCache(userId, _sysRoleRep.Context.CurrentConnectionConfig.ConfigId.ToString()); + } + + var role = await _sysRoleRep.GetFirstAsync(u => u.Id == input.Id); + var dataScope = input.DataScope; + if (!_userManager.SuperAdmin) + { + // 非超级管理员没有全部数据范围权限 + if (dataScope == (int)DataScopeEnum.All) + throw Oops.Oh(ErrorCodeEnum.D1016); + + // 若数据范围自定义,则判断授权数据范围是否有权限 + if (dataScope == (int)DataScopeEnum.Define) + { + var grantOrgIdList = input.OrgIdList; + if (grantOrgIdList.Count > 0) + { + var orgIdList = await _sysOrgService.GetUserOrgIdList(); + if (orgIdList.Count < 1) + throw Oops.Oh(ErrorCodeEnum.D1016); + else if (!grantOrgIdList.All(u => orgIdList.Any(c => c == u))) + throw Oops.Oh(ErrorCodeEnum.D1016); + } + } + } + role.DataScope = (DataScopeEnum)dataScope; + await _sysRoleRep.AsUpdateable(role).UpdateColumns(u => new { u.DataScope }).ExecuteCommandAsync(); + await _sysRoleOrgService.GrantRoleOrg(input); + } + + /// + /// 根据角色Id获取菜单Id集合 🔖 + /// + /// + /// + [DisplayName("根据角色Id获取菜单Id集合")] + public async Task> GetOwnMenuList([FromQuery] RoleInput input) + { + return await _sysRoleMenuService.GetRoleMenuIdList(new List { input.Id }); + } + + /// + /// 根据角色Id获取机构Id集合 🔖 + /// + /// + /// + [DisplayName("根据角色Id获取机构Id集合")] + public async Task> GetOwnOrgList([FromQuery] RoleInput input) + { + return await _sysRoleOrgService.GetRoleOrgIdList(new List { input.Id }); + } + + /// + /// 设置角色状态 🔖 + /// + /// + /// + [DisplayName("设置角色状态")] + public async Task SetStatus(RoleInput input) + { + if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) + throw Oops.Oh(ErrorCodeEnum.D3005); + + return await _sysRoleRep.AsUpdateable() + .SetColumns(u => u.Status == input.Status) + .Where(u => u.Id == input.Id) + .ExecuteCommandAsync(); + } + + /// + /// 授权角色接口资源 🔖 + /// + /// + /// + [UnitOfWork] + [DisplayName("授权角色接口资源")] + public async Task GrantApi(RoleApiInput input) + { + // 删除与该角色相关的用户接口资源缓存 + var userIdList = await _sysUserRoleService.GetUserIdList(input.Id); + foreach (var userId in userIdList) + { + _sysCacheService.Remove(CacheConst.KeyUserApi + userId); + } + + await _sysRoleApiService.GrantRoleApi(input); + } + + /// + /// 根据角色Id获取接口资源集合 🔖 + /// + /// + /// + [DisplayName("根据角色Id获取接口资源集合")] + public async Task> GetOwnApiList([FromQuery] RoleInput input) + { + return await _sysRoleApiService.GetRoleApiList(new List { input.Id }); + } + + /// + /// 获取用户拥有接口资源集合(缓存) 🔖 + /// + /// + [DisplayName("获取用户拥有接口资源集合")] + public async Task> GetUserApiList() + { + var userId = _userManager.UserId; + var apiList = _sysCacheService.Get>(CacheConst.KeyUserApi + userId); + if (apiList == null) + { + // 当前用户已拥有的角色集合 + var roleIds = _userManager.SuperAdmin ? null : await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId); + + apiList = await _sysRoleApiService.GetRoleApiList(roleIds); + _sysCacheService.Set(CacheConst.KeyUserApi + userId, apiList, TimeSpan.FromDays(7)); + } + return apiList; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs b/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs new file mode 100644 index 00000000..d3d8f741 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs @@ -0,0 +1,154 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using AlibabaCloud.SDK.Dysmsapi20170525.Models; +using AngleSharp.Html.Parser; +using AspNet.Security.OAuth.Gitee; +using AspNet.Security.OAuth.Weixin; +using AspNetCoreRateLimit; +using Elastic.Clients.Elasticsearch; +using IPTools.Core; +using Lazy.Captcha.Core; +using Magicodes.ExporterAndImporter.Pdf; +using Magicodes.ExporterAndImporter.Word; +using MailKit.Net.Smtp; +using Novell.Directory.Ldap; +using OnceMi.AspNetCore.OSS; +using QRCoder; + +namespace Admin.NET.Core.Service; + +/// +/// 系统服务器监控服务 🧩 +/// +[ApiDescriptionSettings(Order = 290)] +public class SysServerService : IDynamicApiController, ITransient +{ + public SysServerService() + { + } + + /// + /// 获取服务器配置信息 🔖 + /// + /// + [DisplayName("获取服务器配置信息")] + public dynamic GetServerBase() + { + return new + { + HostName = Environment.MachineName, // 主机名称 + SystemOs = ComputerUtil.GetOSInfo(),//RuntimeInformation.OSDescription, // 操作系统 + OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构 + ProcessorCount = Environment.ProcessorCount + " 核", // CPU核心数 + SysRunTime = ComputerUtil.GetRunTime(), // 系统运行时间 + RemoteIp = ComputerUtil.GetIpFromOnline(), // 外网地址 + LocalIp = App.HttpContext?.Connection?.LocalIpAddress.MapToIPv4().ToString(), // 本地地址 + RuntimeInformation.FrameworkDescription, // NET框架 + Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production", + Wwwroot = App.WebHostEnvironment.WebRootPath, // 网站根目录 + Stage = App.HostEnvironment.IsStaging() ? "Stage环境" : "非Stage环境", // 是否Stage环境 + }; + } + + /// + /// 获取服务器使用信息 🔖 + /// + /// + [DisplayName("获取服务器使用信息")] + public dynamic GetServerUsed() + { + var programStartTime = Process.GetCurrentProcess().StartTime; + var totalMilliseconds = (DateTime.Now - programStartTime).TotalMilliseconds.ToString(); + var ts = totalMilliseconds.Contains('.') ? totalMilliseconds.Split('.')[0] : totalMilliseconds; + var programRunTime = DateTimeUtil.FormatTime(ts.ParseToLong()); + + var memoryMetrics = ComputerUtil.GetComputerInfo(); + return new + { + memoryMetrics.FreeRam, // 空闲内存 + memoryMetrics.UsedRam, // 已用内存 + memoryMetrics.TotalRam, // 总内存 + memoryMetrics.RamRate, // 内存使用率 + memoryMetrics.CpuRate, // Cpu使用率 + StartTime = programStartTime.ToString("yyyy-MM-dd HH:mm:ss"), // 服务启动时间 + RunTime = programRunTime, // 服务运行时间 + }; + } + + /// + /// 获取服务器磁盘信息 🔖 + /// + /// + [DisplayName("获取服务器磁盘信息")] + public dynamic GetServerDisk() + { + return ComputerUtil.GetDiskInfos(); + } + + /// + /// 获取框架主要程序集 🔖 + /// + /// + [DisplayName("获取框架主要程序集")] + public dynamic GetAssemblyList() + { + var furionAssembly = typeof(App).Assembly.GetName(); + var sqlSugarAssembly = typeof(ISqlSugarClient).Assembly.GetName(); + var yitIdAssembly = typeof(YitIdHelper).Assembly.GetName(); + var redisAssembly = typeof(Redis).Assembly.GetName(); + var jsonAssembly = typeof(NewtonsoftJsonMvcCoreBuilderExtensions).Assembly.GetName(); + var excelAssembly = typeof(IExcelImporter).Assembly.GetName(); + var pdfAssembly = typeof(IPdfExporter).Assembly.GetName(); + var wordAssembly = typeof(IWordExporter).Assembly.GetName(); + var captchaAssembly = typeof(ICaptcha).Assembly.GetName(); + var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName(); + var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName(); + var ossAssembly = typeof(IOSSServiceFactory).Assembly.GetName(); + var parserAssembly = typeof(Parser).Assembly.GetName(); + var elasticsearchClientAssembly = typeof(ElasticsearchClient).Assembly.GetName(); + var limitAssembly = typeof(IpRateLimitMiddleware).Assembly.GetName(); + var htmlParserAssembly = typeof(HtmlParser).Assembly.GetName(); + var fluentEmailAssembly = typeof(SmtpClient).Assembly.GetName(); + var qRCodeGeneratorAssembly = typeof(QRCodeGenerator).Assembly.GetName(); + var sendSmsRequestAssembly = typeof(SendSmsRequest).Assembly.GetName(); + var imageAssembly = typeof(Image).Assembly.GetName(); + var rabbitMQAssembly = typeof(RabbitMQEventSourceStore).Assembly.GetName(); + var ldapConnectionAssembly = typeof(LdapConnection).Assembly.GetName(); + var ipToolAssembly = typeof(IpTool).Assembly.GetName(); + var weixinAuthenticationOptionsAssembly = typeof(WeixinAuthenticationOptions).Assembly.GetName(); + var giteeAuthenticationOptionsAssembly = typeof(GiteeAuthenticationOptions).Assembly.GetName(); + + return new[] + { + new { furionAssembly.Name, furionAssembly.Version }, + new { sqlSugarAssembly.Name, sqlSugarAssembly.Version }, + new { yitIdAssembly.Name, yitIdAssembly.Version }, + new { redisAssembly.Name, redisAssembly.Version }, + new { jsonAssembly.Name, jsonAssembly.Version }, + new { excelAssembly.Name, excelAssembly.Version }, + new { pdfAssembly.Name, pdfAssembly.Version }, + new { wordAssembly.Name, wordAssembly.Version }, + new { captchaAssembly.Name, captchaAssembly.Version }, + new { wechatApiAssembly.Name, wechatApiAssembly.Version }, + new { wechatTenpayAssembly.Name, wechatTenpayAssembly.Version }, + new { ossAssembly.Name, ossAssembly.Version }, + new { parserAssembly.Name, parserAssembly.Version }, + new { elasticsearchClientAssembly.Name, elasticsearchClientAssembly.Version }, + new { limitAssembly.Name, limitAssembly.Version }, + new { htmlParserAssembly.Name, htmlParserAssembly.Version }, + new { fluentEmailAssembly.Name, fluentEmailAssembly.Version }, + new { qRCodeGeneratorAssembly.Name, qRCodeGeneratorAssembly.Version }, + new { sendSmsRequestAssembly.Name, sendSmsRequestAssembly.Version }, + new { imageAssembly.Name, imageAssembly.Version }, + new { rabbitMQAssembly.Name, rabbitMQAssembly.Version }, + new { ldapConnectionAssembly.Name, ldapConnectionAssembly.Version }, + new { ipToolAssembly.Name, ipToolAssembly.Version }, + new { weixinAuthenticationOptionsAssembly.Name, weixinAuthenticationOptionsAssembly.Version }, + new { giteeAuthenticationOptionsAssembly.Name, giteeAuthenticationOptionsAssembly.Version }, + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs b/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs new file mode 100644 index 00000000..777fce51 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs @@ -0,0 +1,67 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class TenantInput : BaseIdInput +{ + /// + /// 状态 + /// + public StatusEnum Status { get; set; } +} + +public class PageTenantInput : BasePageInput +{ + /// + /// 名称 + /// + public virtual string Name { get; set; } + + /// + /// 电话 + /// + public virtual string Phone { get; set; } +} + +public class AddTenantInput : TenantOutput +{ + /// + /// 租户名称 + /// + [Required(ErrorMessage = "租户名称不能为空"), MinLength(2, ErrorMessage = "租户名称不能少于2个字符")] + public override string Name { get; set; } + + /// + /// 租管账号 + /// + [Required(ErrorMessage = "租管账号不能为空"), MinLength(3, ErrorMessage = "租管账号不能少于3个字符")] + public override string AdminAccount { get; set; } +} + +public class UpdateTenantInput : AddTenantInput +{ +} + +public class DeleteTenantInput : BaseIdInput +{ +} + +public class TenantUserInput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } +} + +public class TenantIdInput +{ + /// + /// 租户Id + /// + public long TenantId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantOutput.cs b/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantOutput.cs new file mode 100644 index 00000000..7ed20c49 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantOutput.cs @@ -0,0 +1,30 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class TenantOutput : SysTenant +{ + /// + /// 租户名称 + /// + public virtual string Name { get; set; } + + /// + /// 管理员账号 + /// + public virtual string AdminAccount { get; set; } + + /// + /// 电子邮箱 + /// + public string Email { get; set; } + + /// + /// 电话 + /// + public string Phone { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs new file mode 100644 index 00000000..eefa70f4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs @@ -0,0 +1,498 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统租户管理服务 🧩 +/// +[ApiDescriptionSettings(Order = 390)] +public class SysTenantService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysTenantRep; + private readonly SqlSugarRepository _sysOrgRep; + private readonly SqlSugarRepository _sysRoleRep; + private readonly SqlSugarRepository _sysPosRep; + private readonly SqlSugarRepository _sysUserRep; + private readonly SqlSugarRepository _sysUserExtOrgRep; + private readonly SqlSugarRepository _sysRoleMenuRep; + private readonly SqlSugarRepository _userRoleRep; + private readonly SysUserRoleService _sysUserRoleService; + private readonly SysRoleMenuService _sysRoleMenuService; + private readonly SysConfigService _sysConfigService; + private readonly SysCacheService _sysCacheService; + + public SysTenantService(SqlSugarRepository sysTenantRep, + SqlSugarRepository sysOrgRep, + SqlSugarRepository sysRoleRep, + SqlSugarRepository sysPosRep, + SqlSugarRepository sysUserRep, + SqlSugarRepository sysUserExtOrgRep, + SqlSugarRepository sysRoleMenuRep, + SqlSugarRepository userRoleRep, + SysUserRoleService sysUserRoleService, + SysRoleMenuService sysRoleMenuService, + SysConfigService sysConfigService, + SysCacheService sysCacheService) + { + _sysTenantRep = sysTenantRep; + _sysOrgRep = sysOrgRep; + _sysRoleRep = sysRoleRep; + _sysPosRep = sysPosRep; + _sysUserRep = sysUserRep; + _sysUserExtOrgRep = sysUserExtOrgRep; + _sysRoleMenuRep = sysRoleMenuRep; + _userRoleRep = userRoleRep; + _sysUserRoleService = sysUserRoleService; + _sysRoleMenuService = sysRoleMenuService; + _sysConfigService = sysConfigService; + _sysCacheService = sysCacheService; + } + + /// + /// 获取租户分页列表 🔖 + /// + /// + /// + [DisplayName("获取租户分页列表")] + public async Task> Page(PageTenantInput input) + { + return await _sysTenantRep.AsQueryable() + .LeftJoin((u, a) => u.UserId == a.Id) + .LeftJoin((u, a, b) => u.OrgId == b.Id) + .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), (u, a) => a.Phone.Contains(input.Phone.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), (u, a, b) => b.Name.Contains(input.Name.Trim())) + .Where(u => u.Id.ToString() != SqlSugarConst.MainConfigId) // 排除默认主库/主租户 + .OrderBy(u => u.OrderNo) + .Select((u, a, b) => new TenantOutput + { + Id = u.Id, + OrgId = b.Id, + Name = b.Name, + UserId = a.Id, + AdminAccount = a.Account, + Phone = a.Phone, + Email = a.Email, + TenantType = u.TenantType, + DbType = u.DbType, + Connection = u.Connection, + ConfigId = u.ConfigId, + OrderNo = u.OrderNo, + Remark = u.Remark, + Status = u.Status, + CreateTime = u.CreateTime, + CreateUserName = u.CreateUserName, + UpdateTime = u.UpdateTime, + UpdateUserName = u.UpdateUserName, + }) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 获取库隔离的租户列表 + /// + /// + [NonAction] + public async Task> GetTenantDbList() + { + return await _sysTenantRep.GetListAsync(u => u.TenantType == TenantTypeEnum.Db && u.Status == StatusEnum.Enable); + } + + /// + /// 增加租户 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加租户")] + public async Task AddTenant(AddTenantInput input) + { + var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D1300); + + isExist = await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.AdminAccount); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D1301); + + // 从库配置判断 + if (!string.IsNullOrWhiteSpace(input.SlaveConnections) && !JSON.IsValid(input.SlaveConnections)) + throw Oops.Oh(ErrorCodeEnum.D1302); + + switch (input.TenantType) + { + // Id隔离时设置与主库一致 + case TenantTypeEnum.Id: + var config = _sysTenantRep.AsSugarClient().CurrentConnectionConfig; + input.DbType = config.DbType; + input.Connection = config.ConnectionString; + break; + + case TenantTypeEnum.Db: + if (string.IsNullOrWhiteSpace(input.Connection)) + throw Oops.Oh(ErrorCodeEnum.Z1004); + break; + + default: + throw Oops.Oh(ErrorCodeEnum.D3004); + } + + var tenant = input.Adapt(); + await _sysTenantRep.InsertAsync(tenant); + await InitNewTenant(tenant); + + await CacheTenant(); + } + + /// + /// 设置租户状态 🔖 + /// + /// + /// + [DisplayName("设置租户状态")] + public async Task SetStatus(TenantInput input) + { + var tenant = await _sysTenantRep.GetFirstAsync(u => u.Id == input.Id); + if (tenant == null || tenant.ConfigId == SqlSugarConst.MainConfigId) + throw Oops.Oh(ErrorCodeEnum.Z1001); + + if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) + throw Oops.Oh(ErrorCodeEnum.D3005); + + tenant.Status = input.Status; + return await _sysTenantRep.AsUpdateable(tenant).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync(); + } + + /// + /// 新增租户初始化 + /// + /// + private async Task InitNewTenant(TenantOutput tenant) + { + var tenantId = tenant.Id; + var tenantName = tenant.Name; + + // 初始化机构 + var newOrg = new SysOrg + { + Id = tenantId, + TenantId = tenantId, + Pid = 0, + Name = tenantName, + Code = tenantName, + Remark = tenantName, + }; + await _sysOrgRep.InsertAsync(newOrg); + + // 初始化角色 + var newRole = new SysRole + { + Id = tenantId, + TenantId = tenantId, + Name = "租管-" + tenantName, + Code = CommonConst.SysAdminRole, + DataScope = DataScopeEnum.All, + Remark = tenantName + }; + await _sysRoleRep.InsertAsync(newRole); + + // 初始化职位 + var newPos = new SysPos + { + Id = tenantId, + TenantId = tenantId, + Name = "租管-" + tenantName, + Code = tenantName, + Remark = tenantName, + }; + await _sysPosRep.InsertAsync(newPos); + + // 初始化系统账号 + var password = await _sysConfigService.GetConfigValue(CommonConst.SysPassword); + var newUser = new SysUser + { + Id = tenantId, + TenantId = tenantId, + Account = tenant.AdminAccount, + Password = CryptogramUtil.Encrypt(password), + NickName = "租管", + Email = tenant.Email, + Phone = tenant.Phone, + AccountType = AccountTypeEnum.SysAdmin, + OrgId = newOrg.Id, + PosId = newPos.Id, + Birthday = DateTime.Parse("2000-01-01"), + RealName = "租管", + Remark = "租管" + tenantName, + }; + await _sysUserRep.InsertAsync(newUser); + + // 关联用户及角色 + var newUserRole = new SysUserRole + { + RoleId = newRole.Id, + UserId = newUser.Id + }; + await _userRoleRep.InsertAsync(newUserRole); + + // 关联租户组织机构和管理员用户 + await _sysTenantRep.UpdateAsync(u => new SysTenant() { UserId = newUser.Id, OrgId = newOrg.Id }, u => u.Id == tenantId); + + // 默认租户管理员角色菜单集合 + var menuIdList = new List { 1300000000111,1300000000121, // 工作台 + 1310000000111,1310000000112,1310000000113,1310000000114,1310000000115,1310000000116,1310000000117,1310000000118,1310000000119,1310000000120, // 账号 + 1310000000131,1310000000132,1310000000133,1310000000134,1310000000135,1310000000136,1310000000137,1310000000138, // 角色 + 1310000000141,1310000000142,1310000000143,1310000000144,1310000000145, // 机构 + 1310000000151,1310000000152,1310000000153,1310000000154,1310000000155, // 职位 + 1310000000161,1310000000162,1310000000163,1310000000164, // 个人中心 + 1310000000171,1310000000172,1310000000173,1310000000174,1310000000175,1310000000176,1310000000177 // 通知公告 + }; + await _sysRoleMenuService.GrantRoleMenu(new RoleMenuInput() { Id = newRole.Id, MenuIdList = menuIdList }); + } + + /// + /// 删除租户 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除租户")] + public async Task DeleteTenant(DeleteTenantInput input) + { + // 禁止删除默认租户 + if (input.Id.ToString() == SqlSugarConst.MainConfigId) + throw Oops.Oh(ErrorCodeEnum.D1023); + + await _sysTenantRep.DeleteAsync(u => u.Id == input.Id); + + await CacheTenant(input.Id); + + // 删除与租户相关的表数据 + var users = await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.Id).ToListAsync(); + var userIds = users.Select(u => u.Id).ToList(); + await _sysUserRep.AsDeleteable().Where(u => userIds.Contains(u.Id)).ExecuteCommandAsync(); + + await _userRoleRep.AsDeleteable().Where(u => userIds.Contains(u.UserId)).ExecuteCommandAsync(); + + await _sysUserExtOrgRep.AsDeleteable().Where(u => userIds.Contains(u.UserId)).ExecuteCommandAsync(); + + await _sysRoleRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); + + var roleIds = await _sysRoleRep.AsQueryable().ClearFilter() + .Where(u => u.TenantId == input.Id).Select(u => u.Id).ToListAsync(); + await _sysRoleMenuRep.AsDeleteable().Where(u => roleIds.Contains(u.RoleId)).ExecuteCommandAsync(); + + await _sysOrgRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); + + await _sysPosRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); + } + + /// + /// 更新租户 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新租户")] + public async Task UpdateTenant(UpdateTenantInput input) + { + var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.OrgId); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1300); + isExist = await _sysUserRep.IsAnyAsync(u => u.Account == input.AdminAccount && u.Id != input.UserId); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1301); + + // Id隔离时设置与主库一致 + switch (input.TenantType) + { + case TenantTypeEnum.Id: + var config = _sysTenantRep.AsSugarClient().CurrentConnectionConfig; + input.DbType = config.DbType; + input.Connection = config.ConnectionString; + break; + + case TenantTypeEnum.Db: + if (string.IsNullOrWhiteSpace(input.Connection)) + throw Oops.Oh(ErrorCodeEnum.Z1004); + break; + + default: + throw Oops.Oh(ErrorCodeEnum.D3004); + } + // 从库配置判断 + if (!string.IsNullOrWhiteSpace(input.SlaveConnections) && !JSON.IsValid(input.SlaveConnections)) + throw Oops.Oh(ErrorCodeEnum.D1302); + + await _sysTenantRep.AsUpdateable(input.Adapt()).IgnoreColumns(true).ExecuteCommandAsync(); + + // 更新系统机构 + await _sysOrgRep.UpdateAsync(u => new SysOrg() { Name = input.Name }, u => u.Id == input.OrgId); + + // 更新系统用户 + await _sysUserRep.UpdateAsync(u => new SysUser() { Account = input.AdminAccount, Phone = input.Phone, Email = input.Email }, u => u.Id == input.UserId); + + await CacheTenant(input.Id); + } + + /// + /// 授权租户管理员角色菜单 🔖 + /// + /// + /// + [UnitOfWork] + [DisplayName("授权租户管理员角色菜单")] + public async Task GrantMenu(RoleMenuInput input) + { + // 获取租户管理员角色【sys_admin】 + var adminRole = await _sysRoleRep.AsQueryable().ClearFilter() + .FirstAsync(u => u.Code == CommonConst.SysAdminRole && u.TenantId == input.Id && u.IsDelete == false); + if (adminRole == null) return; + + input.Id = adminRole.Id; // 重置租户管理员角色Id + await _sysRoleMenuService.GrantRoleMenu(input); + } + + /// + /// 获取租户管理员角色拥有菜单Id集合 🔖 + /// + /// + /// + [DisplayName("获取租户管理员角色拥有菜单Id集合")] + public async Task> GetOwnMenuList([FromQuery] TenantUserInput input) + { + var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.UserId); + return await _sysRoleMenuService.GetRoleMenuIdList(new List { roleIds[0] }); + } + + /// + /// 重置租户管理员密码 🔖 + /// + /// + /// + [DisplayName("重置租户管理员密码")] + public async Task ResetPwd(TenantUserInput input) + { + var password = await _sysConfigService.GetConfigValue(CommonConst.SysPassword); + var encryptPassword = CryptogramUtil.Encrypt(password); + await _sysUserRep.UpdateAsync(u => new SysUser() { Password = encryptPassword }, u => u.Id == input.UserId); + return password; + } + + /// + /// 缓存所有租户 + /// + /// + /// + [NonAction] + public async Task CacheTenant(long tenantId = 0) + { + // 移除 ISqlSugarClient 中的库连接并排除默认主库 + if (tenantId > 0 && tenantId.ToString() != SqlSugarConst.MainConfigId) + _sysTenantRep.AsTenant().RemoveConnection(tenantId); + + var tenantList = await _sysTenantRep.GetListAsync(); + // 对租户库连接进行SM2加密 + foreach (var tenant in tenantList) + { + if (!string.IsNullOrWhiteSpace(tenant.Connection)) + tenant.Connection = CryptogramUtil.SM2Encrypt(tenant.Connection); + } + _sysCacheService.Set(CacheConst.KeyTenant, tenantList); + } + + /// + /// 创建租户数据库 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "CreateDb"), HttpPost] + [DisplayName("创建租户数据库")] + public async Task CreateDb(TenantInput input) + { + var tenant = await _sysTenantRep.GetSingleAsync(u => u.Id == input.Id); + if (tenant == null) return; + + if (tenant.DbType == SqlSugar.DbType.Oracle) + throw Oops.Oh(ErrorCodeEnum.Z1002); + + if (string.IsNullOrWhiteSpace(tenant.Connection) || tenant.Connection.Length < 10) + throw Oops.Oh(ErrorCodeEnum.Z1004); + + // 默认数据库配置 + var defaultConfig = App.GetOptions().ConnectionConfigs.FirstOrDefault(); + + var config = new DbConnectionConfig + { + ConfigId = tenant.Id.ToString(), + DbType = tenant.DbType, + ConnectionString = tenant.Connection, + DbSettings = new DbSettings() + { + EnableInitDb = true, + EnableDiffLog = false, + EnableUnderLine = defaultConfig.DbSettings.EnableUnderLine, + } + }; + SqlSugarSetup.InitTenantDatabase(App.GetRequiredService().AsTenant(), config); + } + + /// + /// 获取租户下的用户列表 🔖 + /// + /// + /// + [DisplayName("获取租户下的用户列表")] + public async Task> UserList(TenantIdInput input) + { + return await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.TenantId).ToListAsync(); + } + + /// + /// 获取租户数据库连接 + /// + /// + [NonAction] + public SqlSugarScopeProvider GetTenantDbConnectionScope(long tenantId) + { + var iTenant = _sysTenantRep.AsTenant(); + + // 若已存在租户库连接,则直接返回 + if (iTenant.IsAnyConnection(tenantId.ToString())) + return iTenant.GetConnectionScope(tenantId.ToString()); + + lock (iTenant) + { + // 从缓存里面获取租户信息 + var tenant = _sysCacheService.Get>(CacheConst.KeyTenant)?.FirstOrDefault(u => u.Id == tenantId); + if (tenant == null || tenant.TenantType == TenantTypeEnum.Id) return null; + + // 获取默认库连接配置 + var dbOptions = App.GetOptions(); + var mainConnConfig = dbOptions.ConnectionConfigs.First(u => u.ConfigId.ToString() == SqlSugarConst.MainConfigId); + + // 设置租户库连接配置 + var tenantConnConfig = new DbConnectionConfig + { + ConfigId = tenant.Id.ToString(), + DbType = tenant.DbType, + IsAutoCloseConnection = true, + ConnectionString = CryptogramUtil.SM2Decrypt(tenant.Connection), // 对租户库连接进行SM2解密 + DbSettings = new DbSettings() + { + EnableUnderLine = mainConnConfig.DbSettings.EnableUnderLine, + }, + SlaveConnectionConfigs = JSON.IsValid(tenant.SlaveConnections) ? JSON.Deserialize>(tenant.SlaveConnections) : null // 从库连接配置 + }; + iTenant.AddConnection(tenantConnConfig); + + var sqlSugarScopeProvider = iTenant.GetConnectionScope(tenantId.ToString()); + SqlSugarSetup.SetDbConfig(tenantConnConfig); + SqlSugarSetup.SetDbAop(sqlSugarScopeProvider, dbOptions.EnableConsoleSql); + + return sqlSugarScopeProvider; + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/Dto/UserExtOrgInput.cs b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserExtOrgInput.cs new file mode 100644 index 00000000..1a5f9062 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserExtOrgInput.cs @@ -0,0 +1,35 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class UserExtOrgInput : BaseIdInput +{ + /// + /// 机构Id + /// + public long OrgId { get; set; } + + /// + /// 职位Id + /// + public long PosId { get; set; } + + /// + /// 工号 + /// + public string JobNum { get; set; } + + /// + /// 职级 + /// + public string PosLevel { get; set; } + + /// + /// 入职日期 + /// + public DateTime? JoinDate { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/Dto/UserInput.cs b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserInput.cs new file mode 100644 index 00000000..efaf2e86 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserInput.cs @@ -0,0 +1,127 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 设置用户状态输入参数 +/// +public class UserInput : BaseIdInput +{ + /// + /// 状态 + /// + public StatusEnum Status { get; set; } +} + +/// +/// 获取用户分页列表输入参数 +/// +public class PageUserInput : BasePageInput +{ + /// + /// 账号 + /// + public string Account { get; set; } + + /// + /// 姓名 + /// + public string RealName { get; set; } + + /// + /// 手机号 + /// + public string Phone { get; set; } + + /// + /// 查询时所选机构Id + /// + public long OrgId { get; set; } +} + +/// +/// 增加用户输入参数 +/// +public class AddUserInput : SysUser +{ + /// + /// 账号 + /// + [Required(ErrorMessage = "账号不能为空")] + public override string Account { get; set; } + + /// + /// 真实姓名 + /// + [Required(ErrorMessage = "真实姓名不能为空")] + public override string RealName { get; set; } + + /// + /// 域用户 + /// + public string DomainAccount { get; set; } + + /// + /// 角色集合 + /// + public List RoleIdList { get; set; } + + /// + /// 扩展机构集合 + /// + public List ExtOrgIdList { get; set; } +} + +/// +/// 更新用户输入参数 +/// +public class UpdateUserInput : AddUserInput +{ +} + +/// +/// 删除用户输入参数 +/// +public class DeleteUserInput : BaseIdInput +{ + /// + /// 机构Id + /// + public long OrgId { get; set; } +} + +/// +/// 重置用户密码输入参数 +/// +public class ResetPwdUserInput : BaseIdInput +{ +} + +/// +/// 修改用户密码输入参数 +/// +public class ChangePwdInput +{ + /// + /// 当前密码 + /// + [Required(ErrorMessage = "当前密码不能为空")] + public string PasswordOld { get; set; } + + /// + /// 新密码 + /// + [Required(ErrorMessage = "新密码不能为空"), MinLength(5, ErrorMessage = "密码需要大于5个字符")] + public string PasswordNew { get; set; } +} + +/// +/// 解除登录锁定输入参数 +/// +public class UnlockLoginInput : BaseIdInput +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/Dto/UserMenuInput.cs b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserMenuInput.cs new file mode 100644 index 00000000..eecf46e5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserMenuInput.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统用户菜单快捷导航输入 +/// +public class UserMenuInput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 收藏菜单Id集合 + /// + public List MenuIdList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/Dto/UserOutput.cs b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserOutput.cs new file mode 100644 index 00000000..4c508f81 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserOutput.cs @@ -0,0 +1,30 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class UserOutput : SysUser +{ + /// + /// 机构名称 + /// + public string OrgName { get; set; } + + /// + /// 职位名称 + /// + public string PosName { get; set; } + + /// + /// 角色名称 + /// + public string RoleName { get; set; } + + /// + /// 域用户 + /// + public string DomainAccount { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/Dto/UserRoleInput.cs b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserRoleInput.cs new file mode 100644 index 00000000..51827758 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/Dto/UserRoleInput.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 授权用户角色 +/// +public class UserRoleInput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 角色Id集合 + /// + public List RoleIdList { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserExtOrgService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserExtOrgService.cs new file mode 100644 index 00000000..9a963940 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserExtOrgService.cs @@ -0,0 +1,88 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统用户扩展机构服务 +/// +public class SysUserExtOrgService : ITransient +{ + private readonly SqlSugarRepository _sysUserExtOrgRep; + + public SysUserExtOrgService(SqlSugarRepository sysUserExtOrgRep) + { + _sysUserExtOrgRep = sysUserExtOrgRep; + } + + /// + /// 获取用户扩展机构集合 + /// + /// + /// + public async Task> GetUserExtOrgList(long userId) + { + return await _sysUserExtOrgRep.GetListAsync(u => u.UserId == userId); + } + + /// + /// 更新用户扩展机构 + /// + /// + /// + /// + public async Task UpdateUserExtOrg(long userId, List extOrgList) + { + await _sysUserExtOrgRep.DeleteAsync(u => u.UserId == userId); + + if (extOrgList == null || extOrgList.Count < 1) return; + extOrgList.ForEach(u => + { + u.UserId = userId; + }); + await _sysUserExtOrgRep.InsertRangeAsync(extOrgList); + } + + /// + /// 根据机构Id集合删除扩展机构 + /// + /// + /// + public async Task DeleteUserExtOrgByOrgIdList(List orgIdList) + { + await _sysUserExtOrgRep.DeleteAsync(u => orgIdList.Contains(u.OrgId)); + } + + /// + /// 根据用户Id删除扩展机构 + /// + /// + /// + public async Task DeleteUserExtOrgByUserId(long userId) + { + await _sysUserExtOrgRep.DeleteAsync(u => u.UserId == userId); + } + + /// + /// 根据机构Id判断是否有用户 + /// + /// + /// + public async Task HasUserOrg(long orgId) + { + return await _sysUserExtOrgRep.IsAnyAsync(u => u.OrgId == orgId); + } + + /// + /// 根据职位Id判断是否有用户 + /// + /// + /// + public async Task HasUserPos(long posId) + { + return await _sysUserExtOrgRep.IsAnyAsync(u => u.PosId == posId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserLdapService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserLdapService.cs new file mode 100644 index 00000000..1f506616 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserLdapService.cs @@ -0,0 +1,66 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 用户域账号服务 +/// +public class SysUserLdapService : ITransient +{ + private readonly SqlSugarRepository _sysUserLdapRep; + + public SysUserLdapService(SqlSugarRepository sysUserLdapRep) + { + _sysUserLdapRep = sysUserLdapRep; + } + + /// + /// 批量插入域账号 + /// + /// + /// + /// + public async Task InsertUserLdaps(long tenantId, List sysUserLdaps) + { + await _sysUserLdapRep.DeleteAsync(u => u.TenantId == tenantId); + + await _sysUserLdapRep.InsertRangeAsync(sysUserLdaps); + + await _sysUserLdapRep.AsUpdateable() + .InnerJoin((l, u) => l.EmployeeId == u.Account && u.Status == StatusEnum.Enable && u.IsDelete == false && l.IsDelete == false) + .SetColumns((l, u) => new SysUserLdap { UserId = u.Id }) + .ExecuteCommandAsync(); + } + + /// + /// 增加域账号 + /// + /// + /// + /// + /// + /// + public async Task AddUserLdap(long tenantId, long userId, string account, string domainAccount) + { + var userLdap = await _sysUserLdapRep.GetFirstAsync(u => u.TenantId == tenantId && u.IsDelete == false && (u.Account == account || u.UserId == userId || u.EmployeeId == domainAccount)); + if (userLdap != null) + await _sysUserLdapRep.DeleteByIdAsync(userLdap.Id); + + if (!string.IsNullOrWhiteSpace(domainAccount)) + await _sysUserLdapRep.InsertAsync(new SysUserLdap { EmployeeId = account, TenantId = tenantId, UserId = userId, Account = domainAccount }); + } + + /// + /// 删除域账号 + /// + /// + /// + public async Task DeleteUserLdapByUserId(long userId) + { + await _sysUserLdapRep.DeleteAsync(u => u.UserId == userId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserMenuService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserMenuService.cs new file mode 100644 index 00000000..c1f07d21 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserMenuService.cs @@ -0,0 +1,87 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统用户菜单快捷导航服务 🧩 +/// +[ApiDescriptionSettings(Order = 445)] +public class SysUserMenuService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysUserMenuRep; + + public SysUserMenuService(SqlSugarRepository sysUserMenuRep) + { + _sysUserMenuRep = sysUserMenuRep; + } + + /// + /// 收藏菜单 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Add"), HttpPost] + public async Task AddUserMenu(UserMenuInput input) + { + await _sysUserMenuRep.DeleteAsync(u => u.UserId == input.UserId); + + if (input.MenuIdList == null || input.MenuIdList.Count < 1) return; + var menus = input.MenuIdList.Select(u => new SysUserMenu + { + UserId = input.UserId, + MenuId = u + }).ToList(); + await _sysUserMenuRep.InsertRangeAsync(menus); + } + + /// + /// 取消收藏菜单 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + public async Task DeleteUserMenu(UserMenuInput input) + { + await _sysUserMenuRep.DeleteAsync(u => u.UserId == input.UserId && input.MenuIdList.Contains(u.MenuId)); + } + + /// + /// 根据用户Id删除收藏菜单 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "DeleteByUserId"), HttpPost] + public async Task DeleteByUserId(long userId) + { + await _sysUserMenuRep.DeleteAsync(u => u.UserId == userId); + } + + /// + /// 根据用户Id获取收藏菜单集合 🔖 + /// + /// + /// + public async Task> GetUserMenuList(long userId) + { + var sysUserMenuList = await _sysUserMenuRep.AsQueryable() + .Includes(u => u.SysMenu) + .Where(u => u.UserId == userId).ToListAsync(); + return sysUserMenuList.Where(u => u.SysMenu != null).Select(u => u.SysMenu).ToList().Adapt>(); + } + + /// + /// 根据用户Id获取收藏菜单Id集合 🔖 + /// + /// + /// + public async Task> GetUserMenuIdList(long userId) + { + return await _sysUserMenuRep.AsQueryable() + .Where(u => u.UserId == userId).Select(u => u.MenuId).ToListAsync(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserRoleService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserRoleService.cs new file mode 100644 index 00000000..274ade07 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserRoleService.cs @@ -0,0 +1,109 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统用户角色服务 +/// +public class SysUserRoleService : ITransient +{ + private readonly SysCacheService _sysCacheService; + private readonly SqlSugarRepository _sysUserRoleRep; + + public SysUserRoleService(SysCacheService sysCacheService, + SqlSugarRepository sysUserRoleRep) + { + _sysCacheService = sysCacheService; + _sysUserRoleRep = sysUserRoleRep; + } + + /// + /// 授权用户角色 + /// + /// + /// + public async Task GrantUserRole(UserRoleInput input) + { + await _sysUserRoleRep.DeleteAsync(u => u.UserId == input.UserId); + + if (input.RoleIdList == null || input.RoleIdList.Count < 1) return; + var roles = input.RoleIdList.Select(u => new SysUserRole + { + UserId = input.UserId, + RoleId = u + }).ToList(); + await _sysUserRoleRep.InsertRangeAsync(roles); + _sysCacheService.Remove(CacheConst.KeyUserButton + input.UserId); + _sysCacheService.Remove(CacheConst.KeyUserApi + input.UserId); + } + + /// + /// 根据角色Id删除用户角色 + /// + /// + /// + public async Task DeleteUserRoleByRoleId(long roleId) + { + await _sysUserRoleRep.AsQueryable() + .Where(u => u.RoleId == roleId) + .Select(u => u.UserId) + .ForEachAsync(userId => + { + _sysCacheService.Remove(CacheConst.KeyUserButton + userId); + _sysCacheService.Remove(CacheConst.KeyUserApi + userId); + }); + + await _sysUserRoleRep.DeleteAsync(u => u.RoleId == roleId); + } + + /// + /// 根据用户Id删除用户角色 + /// + /// + /// + public async Task DeleteUserRoleByUserId(long userId) + { + await _sysUserRoleRep.DeleteAsync(u => u.UserId == userId); + _sysCacheService.Remove(CacheConst.KeyUserButton + userId); + _sysCacheService.Remove(CacheConst.KeyUserApi + userId); + } + + /// + /// 根据用户Id获取角色集合 + /// + /// + /// + public async Task> GetUserRoleList(long userId) + { + var sysUserRoleList = await _sysUserRoleRep.AsQueryable() + .Includes(u => u.SysRole) + .Where(u => u.UserId == userId).ToListAsync(); + return sysUserRoleList.Where(u => u.SysRole != null).Select(u => u.SysRole).ToList(); + } + + /// + /// 根据用户Id获取角色Id集合 + /// + /// + /// + public async Task> GetUserRoleIdList(long userId) + { + return await _sysUserRoleRep.AsQueryable() + .Where(u => u.UserId == userId).Select(u => u.RoleId).ToListAsync(); + } + + /// + /// 根据角色Id获取用户Id集合 + /// + /// + /// + public async Task> GetUserIdList(long roleId) + { + return await _sysUserRoleRep.AsQueryable() + .Where(u => u.RoleId == roleId).Select(u => u.UserId).ToListAsync(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs new file mode 100644 index 00000000..a56b490b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs @@ -0,0 +1,363 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 系统用户服务 🧩 +/// +[ApiDescriptionSettings(Order = 490)] +public class SysUserService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SysOrgService _sysOrgService; + private readonly SysUserExtOrgService _sysUserExtOrgService; + private readonly SysUserRoleService _sysUserRoleService; + private readonly SysConfigService _sysConfigService; + private readonly SysOnlineUserService _sysOnlineUserService; + private readonly SysCacheService _sysCacheService; + private readonly SysUserLdapService _sysUserLdapService; + private readonly SqlSugarRepository _sysUserRep; + + public SysUserService(UserManager userManager, + SysOrgService sysOrgService, + SysUserExtOrgService sysUserExtOrgService, + SysUserRoleService sysUserRoleService, + SysConfigService sysConfigService, + SysOnlineUserService sysOnlineUserService, + SysCacheService sysCacheService, + SysUserLdapService sysUserLdapService, + SqlSugarRepository sysUserRep) + { + _userManager = userManager; + _sysOrgService = sysOrgService; + _sysUserExtOrgService = sysUserExtOrgService; + _sysUserRoleService = sysUserRoleService; + _sysConfigService = sysConfigService; + _sysOnlineUserService = sysOnlineUserService; + _sysCacheService = sysCacheService; + _sysUserLdapService = sysUserLdapService; + _sysUserRep = sysUserRep; + } + + /// + /// 获取用户分页列表 🔖 + /// + /// + /// + [DisplayName("获取用户分页列表")] + public virtual async Task> Page(PageUserInput input) + { + // 获取用户拥有的机构集合 + var userOrgIdList = await _sysOrgService.GetUserOrgIdList(); + List orgList = null; + if (input.OrgId > 0) // 指定机构查询时 + { + orgList = await _sysOrgService.GetChildIdListWithSelfById(input.OrgId); + orgList = _userManager.SuperAdmin ? orgList : orgList.Where(u => userOrgIdList.Contains(u)).ToList(); + } + else // 各管理员只能看到自己机构下的用户列表 + { + orgList = _userManager.SuperAdmin ? null : userOrgIdList; + } + + return await _sysUserRep.AsQueryable() + .LeftJoin((u, a) => u.OrgId == a.Id) + .LeftJoin((u, a, b) => u.PosId == b.Id) + .Where(u => u.AccountType != AccountTypeEnum.SuperAdmin) + .WhereIF(orgList != null, u => orgList.Contains(u.OrgId)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account.Contains(input.Account)) + .WhereIF(!string.IsNullOrWhiteSpace(input.RealName), u => u.RealName.Contains(input.RealName)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), u => u.Phone.Contains(input.Phone)) + .OrderBy(u => u.OrderNo) + .Select((u, a, b) => new UserOutput + { + OrgName = a.Name, + PosName = b.Name, + RoleName = SqlFunc.Subqueryable().LeftJoin((m, n) => m.RoleId == n.Id).Where(m => m.UserId == u.Id).SelectStringJoin((m, n) => n.Name, ","), + DomainAccount = SqlFunc.Subqueryable().Where(m => m.UserId == u.Id).Select(m => m.Account) + }, true) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加用户 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加用户")] + public virtual async Task AddUser(AddUserInput input) + { + var isExist = await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.Account); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D1003); + + var password = await _sysConfigService.GetConfigValue(CommonConst.SysPassword); + + var user = input.Adapt(); + user.Password = CryptogramUtil.Encrypt(password); + var newUser = await _sysUserRep.AsInsertable(user).ExecuteReturnEntityAsync(); + + input.Id = newUser.Id; + await UpdateRoleAndExtOrg(input); + + // 增加域账号 + if (!string.IsNullOrWhiteSpace(input.DomainAccount)) + await _sysUserLdapService.AddUserLdap(newUser.TenantId.Value, newUser.Id, newUser.Account, input.DomainAccount); + + return newUser.Id; + } + + /// + /// 更新用户 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新用户")] + public virtual async Task UpdateUser(UpdateUserInput input) + { + if (await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.Account && u.Id != input.Id)) + throw Oops.Oh(ErrorCodeEnum.D1003); + + await _sysUserRep.AsUpdateable(input.Adapt()).IgnoreColumns(true) + .IgnoreColumns(u => new { u.Password, u.Status }).ExecuteCommandAsync(); + + await UpdateRoleAndExtOrg(input); + + // 删除用户机构缓存 + SqlSugarFilter.DeleteUserOrgCache(input.Id, _sysUserRep.Context.CurrentConnectionConfig.ConfigId.ToString()); + + // 若账号的角色和组织架构发生变化,则强制下线账号进行权限更新 + var user = await _sysUserRep.AsQueryable().ClearFilter().FirstAsync(u => u.Id == input.Id); + var roleIds = await GetOwnRoleList(input.Id); + if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u))) + await _sysOnlineUserService.ForceOffline(input.Id); + // 更新域账号 + await _sysUserLdapService.AddUserLdap(user.TenantId.Value, user.Id, user.Account, input.DomainAccount); + } + + /// + /// 更新角色和扩展机构 + /// + /// + /// + private async Task UpdateRoleAndExtOrg(AddUserInput input) + { + await GrantRole(new UserRoleInput { UserId = input.Id, RoleIdList = input.RoleIdList }); + + await _sysUserExtOrgService.UpdateUserExtOrg(input.Id, input.ExtOrgIdList); + } + + /// + /// 删除用户 🔖 + /// + /// + /// + [UnitOfWork] + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除用户")] + public virtual async Task DeleteUser(DeleteUserInput input) + { + var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id); + if (user == null) + return; + + if (user.AccountType == AccountTypeEnum.SuperAdmin) + throw Oops.Oh(ErrorCodeEnum.D1014); + if (user.Id == _userManager.UserId) + throw Oops.Oh(ErrorCodeEnum.D1001); + + // 强制下线 + await _sysOnlineUserService.ForceOffline(user.Id); + + await _sysUserRep.DeleteAsync(user); + + // 删除用户角色 + await _sysUserRoleService.DeleteUserRoleByUserId(input.Id); + + // 删除用户扩展机构 + await _sysUserExtOrgService.DeleteUserExtOrgByUserId(input.Id); + + // 删除域账号 + await _sysUserLdapService.DeleteUserLdapByUserId(input.Id); + } + + /// + /// 查看用户基本信息 🔖 + /// + /// + [DisplayName("查看用户基本信息")] + public virtual async Task GetBaseInfo() + { + return await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId); + } + + /// + /// 更新用户基本信息 🔖 + /// + /// + [ApiDescriptionSettings(Name = "BaseInfo"), HttpPost] + [DisplayName("更新用户基本信息")] + public virtual async Task UpdateBaseInfo(SysUser user) + { + return await _sysUserRep.AsUpdateable(user) + .IgnoreColumns(u => new { u.CreateTime, u.Account, u.Password, u.AccountType, u.OrgId, u.PosId }).ExecuteCommandAsync(); + } + + /// + /// 设置用户状态 🔖 + /// + /// + /// + [DisplayName("设置用户状态")] + public virtual async Task SetStatus(UserInput input) + { + if (_userManager.UserId == input.Id) + throw Oops.Oh(ErrorCodeEnum.D1026); + + var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + if (user.AccountType == AccountTypeEnum.SuperAdmin) + throw Oops.Oh(ErrorCodeEnum.D1015); + + if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) + throw Oops.Oh(ErrorCodeEnum.D3005); + + // 账号禁用则增加黑名单,账号启用则移除黑名单 + var sysCacheService = App.GetRequiredService(); + if (input.Status == StatusEnum.Disable) + { + sysCacheService.Set($"{CacheConst.KeyBlacklist}{user.Id}", $"{user.RealName}-{user.Phone}"); + + // 强制下线 + await _sysOnlineUserService.ForceOffline(user.Id); + } + else + { + sysCacheService.Remove($"{CacheConst.KeyBlacklist}{user.Id}"); + } + + user.Status = input.Status; + return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync(); + } + + /// + /// 授权用户角色 🔖 + /// + /// + /// + [UnitOfWork] + [DisplayName("授权用户角色")] + public async Task GrantRole(UserRoleInput input) + { + //var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + //if (user.AccountType == AccountTypeEnum.SuperAdmin) + // throw Oops.Oh(ErrorCodeEnum.D1022); + + await _sysUserRoleService.GrantUserRole(input); + } + + /// + /// 修改用户密码 🔖 + /// + /// + /// + [DisplayName("修改用户密码")] + public virtual async Task ChangePwd(ChangePwdInput input) + { + // 国密SM2解密(前端密码传输SM2加密后的) + input.PasswordOld = CryptogramUtil.SM2Decrypt(input.PasswordOld); + input.PasswordNew = CryptogramUtil.SM2Decrypt(input.PasswordNew); + + var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + { + if (user.Password != MD5Encryption.Encrypt(input.PasswordOld)) + throw Oops.Oh(ErrorCodeEnum.D1004); + } + else + { + if (CryptogramUtil.Decrypt(user.Password) != input.PasswordOld) + throw Oops.Oh(ErrorCodeEnum.D1004); + } + + if (input.PasswordOld == input.PasswordNew) + throw Oops.Oh(ErrorCodeEnum.D1028); + + // 验证密码强度 + if (CryptogramUtil.StrongPassword) + { + user.Password = input.PasswordNew.TryValidate(CryptogramUtil.PasswordStrengthValidation) + ? CryptogramUtil.Encrypt(input.PasswordNew) + : throw Oops.Oh(CryptogramUtil.PasswordStrengthValidationMsg); + } + else + { + user.Password = CryptogramUtil.Encrypt(input.PasswordNew); + } + + return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync(); + } + + /// + /// 重置用户密码 🔖 + /// + /// + /// + [DisplayName("重置用户密码")] + public virtual async Task ResetPwd(ResetPwdUserInput input) + { + var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + var password = await _sysConfigService.GetConfigValue(CommonConst.SysPassword); + user.Password = CryptogramUtil.Encrypt(password); + await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync(); + + // 清空密码错误次数 + var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{user.Account}"; + _sysCacheService.Remove(keyErrorPasswordCount); + + return password; + } + + /// + /// 解除登录锁定 🔖 + /// + /// + /// + [DisplayName("解除登录锁定")] + public virtual async Task UnlockLogin(UnlockLoginInput input) + { + var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009); + + // 清空密码错误次数 + var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{user.Account}"; + _sysCacheService.Remove(keyErrorPasswordCount); + } + + /// + /// 获取用户拥有角色集合 🔖 + /// + /// + /// + [DisplayName("获取用户拥有角色集合")] + public async Task> GetOwnRoleList(long userId) + { + return await _sysUserRoleService.GetUserRoleIdList(userId); + } + + /// + /// 获取用户扩展机构集合 🔖 + /// + /// + /// + [DisplayName("获取用户扩展机构集合")] + public async Task> GetOwnExtOrgList(long userId) + { + return await _sysUserExtOrgService.GetUserExtOrgList(userId); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs b/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs new file mode 100644 index 00000000..de11b8c8 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs @@ -0,0 +1,60 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 当前登录用户 +/// +public class UserManager : IScoped +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// 用户ID + /// + public long UserId => (_httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.UserId)?.Value).ToLong(); + + /// + /// 租户ID + /// + public long TenantId => (_httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.TenantId)?.Value).ToLong(); + + /// + /// 用户账号 + /// + public string Account => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.Account)?.Value; + + /// + /// 真实姓名 + /// + public string RealName => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.RealName)?.Value; + + /// + /// 是否超级管理员 + /// + public bool SuperAdmin => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString(); + + /// + /// 是否系统管理员 + /// + public bool SysAdmin => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SysAdmin).ToString(); + + /// + /// 组织机构Id + /// + public long OrgId => (_httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.OrgId)?.Value).ToLong(); + + /// + /// 微信OpenId + /// + public string OpenId => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.OpenId)?.Value; + + public UserManager(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatInput.cs new file mode 100644 index 00000000..3231f47c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatInput.cs @@ -0,0 +1,109 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 生成网页授权Url +/// +public class GenAuthUrlInput +{ + /// + /// RedirectUrl + /// + public string RedirectUrl { get; set; } + + /// + /// Scope + /// + public string Scope { get; set; } + + /// + /// State + /// + public string State { get; set; } +} + +/// +/// 获取微信用户OpenId +/// +public class WechatOAuth2Input +{ + /// + /// Code + /// + [Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")] + public string Code { get; set; } +} + +/// +/// 微信用户登录 +/// +public class WechatUserLogin +{ + /// + /// OpenId + /// + [Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识长错误")] + public string OpenId { get; set; } +} + +/// +/// 获取配置签名 +/// +public class SignatureInput +{ + /// + /// Url + /// + public string Url { get; set; } +} + +/// +/// 获取消息模板列表 +/// +public class MessageTemplateSendInput +{ + /// + /// 订阅模板Id + /// + [Required(ErrorMessage = "订阅模板Id不能为空")] + public string TemplateId { get; set; } + + /// + /// 接收者的OpenId + /// + [Required(ErrorMessage = "接收者的OpenId不能为空")] + public string ToUserOpenId { get; set; } + + /// + /// 模板数据,格式形如 { "key1": { "value": any }, "key2": { "value": any } } + /// + [Required(ErrorMessage = "模板数据不能为空")] + public Dictionary Data { get; set; } + + /// + /// 模板跳转链接 + /// + public string Url { get; set; } + + /// + /// 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar) + /// + public string MiniProgramPagePath { get; set; } +} + +/// +/// 删除消息模板 +/// +public class DeleteMessageTemplateInput +{ + /// + /// 订阅模板Id + /// + [Required(ErrorMessage = "订阅模板Id不能为空")] + public string TemplateId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs new file mode 100644 index 00000000..4d29621a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class WechatPayTransactionInput +{ + /// + /// OpenId + /// + public string OpenId { get; set; } + + /// + /// 订单金额 + /// + public int Total { get; set; } + + /// + /// 商品描述 + /// + public string Description { get; set; } + + /// + /// 附加数据 + /// + public string Attachment { get; set; } + + /// + /// 优惠标记 + /// + public string GoodsTag { get; set; } +} + +public class WechatPayParaInput +{ + /// + /// 订单Id + /// + public string PrepayId { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs new file mode 100644 index 00000000..b00e16aa --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs @@ -0,0 +1,30 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class WechatPayOutput +{ + /// + /// OpenId + /// + public string OpenId { get; set; } + + /// + /// 订单金额 + /// + public int Total { get; set; } + + /// + /// 附加数据 + /// + public string Attachment { get; set; } + + /// + /// 优惠标记 + /// + public string GoodsTag { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs new file mode 100644 index 00000000..0ec4cede --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs @@ -0,0 +1,118 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 获取微信用户OpenId +/// +public class JsCode2SessionInput +{ + /// + /// JsCode + /// + [Required(ErrorMessage = "JsCode不能为空"), MinLength(10, ErrorMessage = "JsCode错误")] + public string JsCode { get; set; } +} + +/// +/// 获取微信用户电话号码 +/// +public class WxPhoneInput : WxOpenIdLoginInput +{ + /// + /// Code + /// + [Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")] + public string Code { get; set; } +} + +/// +/// 微信小程序登录 +/// +public class WxOpenIdLoginInput +{ + /// + /// OpenId + /// + [Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识错误")] + public string OpenId { get; set; } +} + +/// +/// 微信手机号登录 +/// +public class WxPhoneLoginInput +{ + /// + /// 电话号码 + /// + [DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "电话号码错误")] + public string PhoneNumber { get; set; } +} + +/// +/// 发送订阅消息 +/// +public class SendSubscribeMessageInput +{ + /// + /// 订阅模板Id + /// + [Required(ErrorMessage = "订阅模板Id不能为空")] + public string TemplateId { get; set; } + + /// + /// 接收者的OpenId + /// + [Required(ErrorMessage = "接收者的OpenId不能为空")] + public string ToUserOpenId { get; set; } + + /// + /// 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } + /// + [Required(ErrorMessage = "模板内容不能为空")] + public Dictionary Data { get; set; } + + /// + /// 跳转小程序类型 + /// + public string MiniprogramState { get; set; } + + /// + /// 语言类型 + /// + public string Language { get; set; } + + /// + /// 点击模板卡片后的跳转页面(仅限本小程序内的页面),支持带参数(示例pages/app/index?foo=bar) + /// + public string MiniProgramPagePath { get; set; } +} + +/// +/// 增加订阅消息模板 +/// +public class AddSubscribeMessageTemplateInput +{ + /// + /// 模板标题Id + /// + [Required(ErrorMessage = "模板标题Id不能为空")] + public string TemplateTitleId { get; set; } + + /// + /// 模板关键词列表,例如 [3,5,4] + /// + [Required(ErrorMessage = "模板关键词列表不能为空")] + public List KeyworkIdList { get; set; } + + /// + /// 服务场景描述,15个字以内 + /// + [Required(ErrorMessage = "服务场景描述不能为空")] + public string SceneDescription { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenOutput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenOutput.cs new file mode 100644 index 00000000..582131f8 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenOutput.cs @@ -0,0 +1,17 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class WxOpenIdOutput +{ + public string OpenId { get; set; } +} + +public class WxPhoneOutput +{ + public string PhoneNumber { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs new file mode 100644 index 00000000..957af35e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs @@ -0,0 +1,251 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 微信支付服务 🧩 +/// +[ApiDescriptionSettings(Order = 210)] +public class SysWechatPayService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysWechatPayUserRep; + + private readonly WechatPayOptions _wechatPayOptions; + private readonly PayCallBackOptions _payCallBackOptions; + + private readonly WechatTenpayClient _wechatTenpayClient; + + public SysWechatPayService(SqlSugarRepository sysWechatPayUserRep, + IOptions wechatPayOptions, + IOptions payCallBackOptions) + { + _sysWechatPayUserRep = sysWechatPayUserRep; + _wechatPayOptions = wechatPayOptions.Value; + _payCallBackOptions = payCallBackOptions.Value; + + _wechatTenpayClient = CreateTenpayClient(); + } + + /// + /// 初始化微信支付客户端 + /// + /// + private WechatTenpayClient CreateTenpayClient() + { + var cerFilePath = App.WebHostEnvironment.ContentRootPath + _wechatPayOptions.MerchantCertificatePrivateKey; + + var tenpayClientOptions = new WechatTenpayClientOptions() + { + MerchantId = _wechatPayOptions.MerchantId, + MerchantV3Secret = _wechatPayOptions.MerchantV3Secret, + MerchantCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber, + MerchantCertificatePrivateKey = File.Exists(cerFilePath) ? File.ReadAllText(cerFilePath) : "", + PlatformCertificateManager = new InMemoryCertificateManager() + }; + return new WechatTenpayClient(tenpayClientOptions); + } + + /// + /// 生成JSAPI调起支付所需参数 🔖 + /// + /// + /// + [DisplayName("生成JSAPI调起支付所需参数")] + public dynamic GenerateParametersForJsapiPay(WechatPayParaInput input) + { + return _wechatTenpayClient.GenerateParametersForJsapiPayRequest(_wechatPayOptions.AppId, input.PrepayId); + } + + /// + /// 微信支付统一下单获取Id(商户直连) 🔖 + /// + [DisplayName("微信支付统一下单获取Id(商户直连)")] + public async Task CreatePayTransaction([FromBody] WechatPayTransactionInput input) + { + var request = new CreatePayTransactionJsapiRequest() + { + OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + AppId = _wechatPayOptions.AppId, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + ExpireTime = DateTimeOffset.Now.AddMinutes(10), + NotifyUrl = _payCallBackOptions.WechatPayUrl, + Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = input.Total }, + Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId } + }; + var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request); + if (!response.IsSuccessful()) + throw Oops.Oh(response.ErrorMessage); + + // 保存订单信息 + var wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "" + }; + await _sysWechatPayUserRep.InsertAsync(wechatPay); + + return new + { + response.PrepayId, + request.OutTradeNumber + }; + } + + /// + /// 微信支付统一下单获取Id(服务商模式) 🔖 + /// + [DisplayName("微信支付统一下单获取Id(服务商模式)")] + public async Task CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input) + { + var request = new CreatePayPartnerTransactionJsapiRequest() + { + OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + SubAppId = _wechatPayOptions.AppId, + SubMerchantId = _wechatPayOptions.MerchantId, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + ExpireTime = DateTimeOffset.Now.AddMinutes(10), + NotifyUrl = _payCallBackOptions.WechatPayUrl, + Amount = new CreatePayPartnerTransactionJsapiRequest.Types.Amount() { Total = input.Total }, + Payer = new CreatePayPartnerTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId } + }; + var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request); + if (!response.IsSuccessful()) + throw Oops.Oh(response.ErrorMessage); + + // 保存订单信息 + var wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + SubAppId = _wechatPayOptions.AppId, + SubMerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "" + }; + await _sysWechatPayUserRep.InsertAsync(wechatPay); + + return new + { + response.PrepayId, + request.OutTradeNumber + }; + } + + /// + /// 获取支付订单详情 🔖 + /// + /// + /// + [DisplayName("获取支付订单详情")] + public async Task GetPayInfo(string tradeId) + { + return await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); + } + + /// + /// 微信支付成功回调(商户直连) 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("微信支付成功回调(商户直连)")] + public async Task PayCallBack() + { + using var ms = new MemoryStream(); + await App.HttpContext.Request.Body.CopyToAsync(ms); + var b = ms.ToArray(); + var callbackJson = Encoding.UTF8.GetString(b); + + var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson); + if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType)) + { + var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); + + // 修改订单支付状态 + var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + && u.MerchantId == callbackResource.MerchantId); + if (wechatPay == null) return null; + //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 + //wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号 + //wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号 + wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号 + wechatPay.TradeType = callbackResource.TradeType; // 交易类型 + wechatPay.TradeState = callbackResource.TradeState; // 交易状态 + wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = callbackResource.BankType; // 付款银行类型 + wechatPay.Total = callbackResource.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 + + await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + + return new WechatPayOutput() + { + Total = wechatPay.Total, + Attachment = wechatPay.Attachment, + GoodsTag = wechatPay.GoodsTag + }; + } + + return null; + } + + /// + /// 微信支付成功回调(服务商模式) 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("微信支付成功回调(服务商模式)")] + public async Task PayPartnerCallBack() + { + using var ms = new MemoryStream(); + await App.HttpContext.Request.Body.CopyToAsync(ms); + var b = ms.ToArray(); + var callbackJson = Encoding.UTF8.GetString(b); + + var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson); + if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType)) + { + var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); + + // 修改订单支付状态 + var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + && u.MerchantId == callbackResource.MerchantId); + if (wechatPay == null) return; + //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 + //wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号 + //wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号 + wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号 + wechatPay.TradeType = callbackResource.TradeType; // 交易类型 + wechatPay.TradeState = callbackResource.TradeState; // 交易状态 + wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = callbackResource.BankType; // 付款银行类型 + wechatPay.Total = callbackResource.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 + + await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatService.cs new file mode 100644 index 00000000..99ae2767 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatService.cs @@ -0,0 +1,220 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 微信公众号服务 🧩 +/// +[ApiDescriptionSettings(Order = 230)] +public class SysWechatService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysOAuthUserRep; + private readonly SysConfigService _sysConfigService; + private readonly WechatApiClientFactory _wechatApiClientFactory; + private readonly WechatApiClient _wechatApiClient; + private readonly SysCacheService _sysCacheService; + + public SysWechatService(SqlSugarRepository sysOAuthUserRep, + SysConfigService sysConfigService, + WechatApiClientFactory wechatApiClientFactory, + SysCacheService sysCacheService) + { + _sysOAuthUserRep = sysOAuthUserRep; + _sysConfigService = sysConfigService; + _wechatApiClientFactory = wechatApiClientFactory; + _wechatApiClient = wechatApiClientFactory.CreateWechatClient(); + _sysCacheService = sysCacheService; + } + + /// + /// 生成网页授权Url 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("生成网页授权Url")] + public string GenAuthUrl(GenAuthUrlInput input) + { + return _wechatApiClient.GenerateParameterizedUrlForConnectOAuth2Authorize(input.RedirectUrl, input.Scope, input.State); + } + + /// + /// 获取微信用户OpenId 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("获取微信用户OpenId")] + public async Task SnsOAuth2([FromQuery] WechatOAuth2Input input) + { + var reqOAuth2 = new SnsOAuth2AccessTokenRequest() + { + Code = input.Code, + }; + var resOAuth2 = await _wechatApiClient.ExecuteSnsOAuth2AccessTokenAsync(reqOAuth2); + if (resOAuth2.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resOAuth2.ErrorMessage + " " + resOAuth2.ErrorCode); + + var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == resOAuth2.OpenId); + if (wxUser == null) + { + var reqUserInfo = new SnsUserInfoRequest() + { + OpenId = resOAuth2.OpenId, + AccessToken = resOAuth2.AccessToken, + }; + var resUserInfo = await _wechatApiClient.ExecuteSnsUserInfoAsync(reqUserInfo); + wxUser = resUserInfo.Adapt(); + wxUser.Avatar = resUserInfo.HeadImageUrl; + wxUser.NickName = resUserInfo.Nickname; + wxUser = await _sysOAuthUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync(); + } + else + { + wxUser.AccessToken = resOAuth2.AccessToken; + wxUser.RefreshToken = resOAuth2.RefreshToken; + await _sysOAuthUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync(); + } + + return resOAuth2.OpenId; + } + + /// + /// 微信用户登录OpenId 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("微信用户登录OpenId")] + public async Task OpenIdLogin(WechatUserLogin input) + { + var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == input.OpenId); + if (wxUser == null) + throw Oops.Oh("微信用户登录OpenId错误"); + + var tokenExpire = await _sysConfigService.GetTokenExpire(); + return new + { + wxUser.Avatar, + accessToken = JWTEncryption.Encrypt(new Dictionary + { + { ClaimConst.UserId, wxUser.Id }, + { ClaimConst.NickName, wxUser.NickName }, + { ClaimConst.LoginMode, LoginModeEnum.APP }, + }, tokenExpire) + }; + } + + /// + /// 获取配置签名参数(wx.config) 🔖 + /// + /// + [DisplayName("获取配置签名参数(wx.config)")] + public async Task GenConfigPara(SignatureInput input) + { + var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(new CgibinTokenRequest()); + var request = new CgibinTicketGetTicketRequest() + { + AccessToken = resCgibinToken.AccessToken + }; + var response = await _wechatApiClient.ExecuteCgibinTicketGetTicketAsync(request); + if (!response.IsSuccessful()) + throw Oops.Oh(response.ErrorMessage); + return _wechatApiClient.GenerateParametersForJSSDKConfig(response.Ticket, input.Url); + } + + /// + /// 获取模板列表 🔖 + /// + [DisplayName("获取模板列表")] + public async Task GetMessageTemplateList() + { + var accessToken = await GetCgibinToken(); + var reqTemplate = new CgibinTemplateGetAllPrivateTemplateRequest() + { + AccessToken = accessToken + }; + var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateGetAllPrivateTemplateAsync(reqTemplate); + if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode); + + return resTemplate.TemplateList; + } + + /// + /// 发送模板消息 🔖 + /// + /// + /// + [DisplayName("发送模板消息")] + public async Task SendTemplateMessage(MessageTemplateSendInput input) + { + var dataInfo = input.Data.ToDictionary(k => k.Key, k => k.Value); + var messageData = new Dictionary(); + foreach (var item in dataInfo) + { + messageData.Add(item.Key, new CgibinMessageTemplateSendRequest.Types.DataItem() { Value = "" + item.Value.Value.ToString() + "" }); + } + + var accessToken = await GetCgibinToken(); + var reqMessage = new CgibinMessageTemplateSendRequest() + { + AccessToken = accessToken, + TemplateId = input.TemplateId, + ToUserOpenId = input.ToUserOpenId, + Url = input.Url, + MiniProgram = new CgibinMessageTemplateSendRequest.Types.MiniProgram + { + AppId = _wechatApiClientFactory._wechatOptions.WechatAppId, + PagePath = input.MiniProgramPagePath, + }, + Data = messageData + }; + var resMessage = await _wechatApiClient.ExecuteCgibinMessageTemplateSendAsync(reqMessage); + return resMessage; + } + + /// + /// 删除模板 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "DeleteMessageTemplate"), HttpPost] + [DisplayName("删除模板")] + public async Task DeleteMessageTemplate(DeleteMessageTemplateInput input) + { + var accessToken = await GetCgibinToken(); + var reqMessage = new CgibinTemplateDeletePrivateTemplateRequest() + { + AccessToken = accessToken, + TemplateId = input.TemplateId + }; + var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateDeletePrivateTemplateAsync(reqMessage); + return resTemplate; + } + + /// + /// 获取Access_token + /// + private async Task GetCgibinToken() + { + // 先从缓存中取 AccessToken + var wx_accessToken = _sysCacheService.Get("sys_wx_accessToken"); + if (!string.IsNullOrWhiteSpace(wx_accessToken)) + { + return wx_accessToken; + } + + // 若缓存没有则从微信公众号重新获取 AccessToken + var reqCgibinToken = new CgibinTokenRequest(); + var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(reqCgibinToken); + if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode); + + _sysCacheService.Set("sys_wx_accessToken", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn)); // 缓存 AccessToken + return resCgibinToken.AccessToken; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs new file mode 100644 index 00000000..9dfd2e08 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs @@ -0,0 +1,190 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 微信小程序服务 🧩 +/// +[ApiDescriptionSettings(Order = 240)] +public class SysWxOpenService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _sysOAuthUserRep; + private readonly SysConfigService _sysConfigService; + private readonly WechatApiClient _wechatApiClient; + + public SysWxOpenService(SqlSugarRepository sysOAuthUserRep, + SysConfigService sysConfigService, + WechatApiClientFactory wechatApiClientFactory) + { + _sysOAuthUserRep = sysOAuthUserRep; + _sysConfigService = sysConfigService; + _wechatApiClient = wechatApiClientFactory.CreateWxOpenClient(); + } + + /// + /// 获取微信用户OpenId 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("获取微信用户OpenId")] + public async Task GetWxOpenId([FromQuery] JsCode2SessionInput input) + { + var reqJsCode2Session = new SnsJsCode2SessionRequest() + { + JsCode = input.JsCode, + }; + var resCode2Session = await _wechatApiClient.ExecuteSnsJsCode2SessionAsync(reqJsCode2Session); + if (resCode2Session.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resCode2Session.ErrorMessage + " " + resCode2Session.ErrorCode); + + var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == resCode2Session.OpenId); + if (wxUser == null) + { + wxUser = new SysOAuthUser + { + OpenId = resCode2Session.OpenId, + UnionId = resCode2Session.UnionId, + SessionKey = resCode2Session.SessionKey, + PlatformType = PlatformTypeEnum.微信小程序 + }; + wxUser = await _sysOAuthUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync(); + } + else + { + await _sysOAuthUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync(); + } + + return new WxOpenIdOutput + { + OpenId = resCode2Session.OpenId + }; + } + + /// + /// 获取微信用户电话号码 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("获取微信用户电话号码")] + public async Task GetWxPhone([FromQuery] WxPhoneInput input) + { + var accessToken = await GetCgibinToken(); + var reqUserPhoneNumber = new WxaBusinessGetUserPhoneNumberRequest() + { + Code = input.Code, + AccessToken = accessToken, + }; + var resUserPhoneNumber = await _wechatApiClient.ExecuteWxaBusinessGetUserPhoneNumberAsync(reqUserPhoneNumber); + if (resUserPhoneNumber.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resUserPhoneNumber.ErrorMessage + " " + resUserPhoneNumber.ErrorCode); + + return new WxPhoneOutput + { + PhoneNumber = resUserPhoneNumber.PhoneInfo?.PhoneNumber + }; + } + + /// + /// 微信小程序登录OpenId 🔖 + /// + /// + /// + [AllowAnonymous] + [DisplayName("微信小程序登录OpenId")] + public async Task WxOpenIdLogin(WxOpenIdLoginInput input) + { + var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == input.OpenId); + if (wxUser == null) + throw Oops.Oh("微信小程序登录失败"); + + var tokenExpire = await _sysConfigService.GetTokenExpire(); + return new + { + wxUser.Avatar, + accessToken = JWTEncryption.Encrypt(new Dictionary + { + { ClaimConst.UserId, wxUser.Id }, + { ClaimConst.RealName, wxUser.NickName }, + { ClaimConst.LoginMode, LoginModeEnum.APP }, + }, tokenExpire) + }; + } + + /// + /// 获取订阅消息模板列表 🔖 + /// + [DisplayName("获取订阅消息模板列表")] + public async Task GetMessageTemplateList() + { + var accessToken = await GetCgibinToken(); + var reqTemplate = new WxaApiNewTemplateGetTemplateRequest() + { + AccessToken = accessToken + }; + var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateGetTemplateAsync(reqTemplate); + if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode); + + return resTemplate.TemplateList; + } + + /// + /// 发送订阅消息 🔖 + /// + /// + /// + [DisplayName("发送订阅消息")] + public async Task SendSubscribeMessage(SendSubscribeMessageInput input) + { + var accessToken = await GetCgibinToken(); + var reqMessage = new CgibinMessageSubscribeSendRequest() + { + AccessToken = accessToken, + TemplateId = input.TemplateId, + ToUserOpenId = input.ToUserOpenId, + Data = input.Data, + MiniProgramState = input.MiniprogramState, + Language = input.Language, + MiniProgramPagePath = input.MiniProgramPagePath + }; + var resMessage = await _wechatApiClient.ExecuteCgibinMessageSubscribeSendAsync(reqMessage); + return resMessage; + } + + /// + /// 增加订阅消息模板 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "AddSubscribeMessageTemplate"), HttpPost] + [DisplayName("增加订阅消息模板")] + public async Task AddSubscribeMessageTemplate(AddSubscribeMessageTemplateInput input) + { + var accessToken = await GetCgibinToken(); + var reqMessage = new WxaApiNewTemplateAddTemplateRequest() + { + AccessToken = accessToken, + TemplateTitleId = input.TemplateTitleId, + KeyworkIdList = input.KeyworkIdList, + SceneDescription = input.SceneDescription + }; + var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateAddTemplateAsync(reqMessage); + return resTemplate; + } + + /// + /// 获取Access_token + /// + private async Task GetCgibinToken() + { + var reqCgibinToken = new CgibinTokenRequest(); + var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(reqCgibinToken); + if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功) + throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode); + return resCgibinToken.AccessToken; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/WechatApiHttpClient.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatApiHttpClient.cs new file mode 100644 index 00000000..d6d06fec --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatApiHttpClient.cs @@ -0,0 +1,84 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; + +namespace Admin.NET.Core.Service; + +/// +/// 微信API客户端 +/// +public partial class WechatApiClientFactory : ISingleton +{ + private readonly IHttpClientFactory _httpClientFactory; + public readonly WechatOptions _wechatOptions; + + public WechatApiClientFactory(IHttpClientFactory httpClientFactory, IOptions wechatOptions) + { + _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + _wechatOptions = wechatOptions.Value ?? throw new ArgumentNullException(nameof(wechatOptions)); + } + + /// + /// 微信公众号 + /// + /// + public WechatApiClient CreateWechatClient() + { + if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret)) + throw Oops.Oh("微信公众号配置错误"); + + var client = WechatApiClientBuilder.Create(new WechatApiClientOptions() + { + AppId = _wechatOptions.WechatAppId, + AppSecret = _wechatOptions.WechatAppSecret, + PushToken = _wechatOptions.WechatToken, + PushEncodingAESKey = _wechatOptions.WechatEncodingAESKey, + }) + .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁 + .Build(); + + client.Configure(config => + { + JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings(); + jsonSerializerSettings.Formatting = Formatting.Indented; + config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化 + // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化 + }); + + return client; + } + + /// + /// 微信小程序 + /// + /// + public WechatApiClient CreateWxOpenClient() + { + if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret)) + throw Oops.Oh("微信小程序配置错误"); + + var client = WechatApiClientBuilder.Create(new WechatApiClientOptions() + { + AppId = _wechatOptions.WxOpenAppId, + AppSecret = _wechatOptions.WxOpenAppSecret, + PushToken = _wechatOptions.WxToken, + PushEncodingAESKey = _wechatOptions.WxEncodingAESKey, + }) + .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁 + .Build(); + + client.Configure(config => + { + JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings(); + jsonSerializerSettings.Formatting = Formatting.Indented; + config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化 + // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化 + }); + + return client; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignalR/SignalRSetup.cs b/Admin.NET/Admin.NET.Core/SignalR/SignalRSetup.cs new file mode 100644 index 00000000..7029ac36 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignalR/SignalRSetup.cs @@ -0,0 +1,104 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion.Logging.Extensions; +using Microsoft.AspNetCore.DataProtection; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace Admin.NET.Core; + +public static class SignalRSetup +{ + /// + /// 即时消息SignalR注册 + /// + /// + /// + public static void AddSignalR(this IServiceCollection services, Action SetNewtonsoftJsonSetting) + { + var signalRBuilder = services.AddSignalR(options => + { + options.EnableDetailedErrors = true; + options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); + options.KeepAliveInterval = TimeSpan.FromMinutes(1); + options.MaximumReceiveMessageSize = 1024 * 1024 * 10; // 数据包大小10M,默认最大为32K + }).AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings)); + + // 若未启用Redis缓存,直接返回 + var cacheOptions = App.GetConfig("Cache", true); + if (cacheOptions.CacheType != CacheTypeEnum.Redis.ToString()) + return; + + // 若已开启集群配置,则把SignalR配置为支持集群模式 + var clusterOpt = App.GetConfig("Cluster", true); + if (!clusterOpt.Enabled) + return; + + var redisOptions = clusterOpt.SentinelConfig; + ConnectionMultiplexer connection1; + if (clusterOpt.IsSentinel) // 哨兵模式 + { + var redisConfig = new ConfigurationOptions + { + AbortOnConnectFail = false, + ServiceName = redisOptions.ServiceName, + AllowAdmin = true, + DefaultDatabase = redisOptions.DefaultDb, + Password = redisOptions.Password + }; + redisOptions.EndPoints.ForEach(u => redisConfig.EndPoints.Add(u)); + connection1 = ConnectionMultiplexer.Connect(redisConfig); + } + else + { + connection1 = ConnectionMultiplexer.Connect(clusterOpt.SignalR.RedisConfiguration); + } + // 密钥存储(数据保护) + services.AddDataProtection().PersistKeysToStackExchangeRedis(connection1, clusterOpt.DataProtecteKey); + + signalRBuilder.AddStackExchangeRedis(options => + { + // 此处设置的ChannelPrefix并不会生效,如果两个不同的项目,且[程序集名+类名]一样,使用同一个redis服务,请注意修改 Hub/OnlineUserHub 的类名。 + // 原因请参考下边链接: + // https://github.com/dotnet/aspnetcore/blob/f9121bc3e976ec40a959818451d126d5126ce868/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs#L74 + // https://github.com/dotnet/aspnetcore/blob/f9121bc3e976ec40a959818451d126d5126ce868/src/SignalR/server/StackExchangeRedis/src/Internal/RedisChannels.cs#L33 + options.Configuration.ChannelPrefix = new RedisChannel(clusterOpt.SignalR.ChannelPrefix, RedisChannel.PatternMode.Auto); + options.ConnectionFactory = async writer => + { + ConnectionMultiplexer connection; + if (clusterOpt.IsSentinel) + { + var config = new ConfigurationOptions + { + AbortOnConnectFail = false, + ServiceName = redisOptions.ServiceName, + AllowAdmin = true, + DefaultDatabase = redisOptions.DefaultDb, + Password = redisOptions.Password + }; + redisOptions.EndPoints.ForEach(u => config.EndPoints.Add(u)); + connection = await ConnectionMultiplexer.ConnectAsync(config, writer); + } + else + { + connection = await ConnectionMultiplexer.ConnectAsync(clusterOpt.SignalR.RedisConfiguration); + } + + connection.ConnectionFailed += (_, e) => + { + "连接 Redis 失败".LogError(); + }; + + if (!connection.IsConnected) + { + "无法连接 Redis".LogError(); + } + return connection; + }; + }); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/GetAccessSecretContext.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/GetAccessSecretContext.cs new file mode 100644 index 00000000..1708cf33 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/GetAccessSecretContext.cs @@ -0,0 +1,27 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +/// +/// 获取 AccessKey 关联 AccessSecret 方法的上下文 +/// +public class GetAccessSecretContext : BaseContext +{ + public GetAccessSecretContext(HttpContext context, + AuthenticationScheme scheme, + SignatureAuthenticationOptions options) + : base(context, scheme, options) + { + } + + /// + /// 身份标识 + /// + public string AccessKey { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationDefaults.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationDefaults.cs new file mode 100644 index 00000000..ce42ff70 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationDefaults.cs @@ -0,0 +1,23 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证处理程序相关的默认值 +/// +public static class SignatureAuthenticationDefaults +{ + /// + /// SignatureAuthenticationOptions.AuthenticationScheme 使用的默认值 + /// + public const string AuthenticationScheme = "Signature"; + + /// + /// 附加在 HttpContext Item 中验证失败消息的 Key + /// + public const string AuthenticateFailMsgKey = "SignatureAuthenticateFailMsg"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationEvent.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationEvent.cs new file mode 100644 index 00000000..281ced8d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationEvent.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证事件 +/// +public class SignatureAuthenticationEvent +{ + public SignatureAuthenticationEvent() + { + } + + /// + /// 获取或设置获取 AccessKey 的 AccessSecret 的逻辑处理 + /// + public Func> OnGetAccessSecret { get; set; } + + /// + /// 获取或设置质询的逻辑处理 + /// + public Func OnChallenge { get; set; } = _ => Task.CompletedTask; + + /// + /// 获取或设置已验证的逻辑处理 + /// + public Func OnValidated { get; set; } = _ => Task.CompletedTask; + + /// + /// 获取 AccessKey 的 AccessSecret + /// + /// + /// + public virtual Task GetAccessSecret(GetAccessSecretContext context) => OnGetAccessSecret?.Invoke(context) ?? throw new NotImplementedException($"需要提供 {nameof(OnGetAccessSecret)} 实现"); + + /// + /// 质询 + /// + /// + /// + public virtual Task Challenge(SignatureChallengeContext context) => OnChallenge?.Invoke(context) ?? Task.CompletedTask; + + /// + /// 已验证成功 + /// + /// + /// + public virtual Task Validated(SignatureValidatedContext context) => OnValidated?.Invoke(context) ?? Task.CompletedTask; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationExtensions.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationExtensions.cs new file mode 100644 index 00000000..39105489 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationExtensions.cs @@ -0,0 +1,36 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证扩展 +/// +public static class SignatureAuthenticationExtensions +{ + /// + /// 注册 Signature 身份验证处理模块 + /// + /// + /// + public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder) + { + return builder.AddSignatureAuthentication(options => { }); + } + + /// + /// 注册 Signature 身份验证处理模块 + /// + /// + /// + /// + public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder, Action options) + { + return builder.AddScheme(SignatureAuthenticationDefaults.AuthenticationScheme, options); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs new file mode 100644 index 00000000..e6beeae6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs @@ -0,0 +1,175 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text.Encodings.Web; + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证处理 +/// +public sealed class SignatureAuthenticationHandler : AuthenticationHandler +{ +#if NET6_0 + + public SignatureAuthenticationHandler(IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) + : base(options, logger, encoder, clock) + { + } + +#else + public SignatureAuthenticationHandler(IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) + : base(options, logger, encoder) + { + } +#endif + + private new SignatureAuthenticationEvent Events + { + get => (SignatureAuthenticationEvent)base.Events; + set => base.Events = value; + } + + /// + /// 确保创建的 Event 类型是 DigestEvents + /// + /// + protected override Task CreateEventsAsync() => throw new NotImplementedException($"{nameof(SignatureAuthenticationOptions)}.{nameof(SignatureAuthenticationOptions.Events)} 需要提供一个实例"); + + protected override async Task HandleAuthenticateAsync() + { + var accessKey = Request.Headers["accessKey"].FirstOrDefault(); + var timestampStr = Request.Headers["timestamp"].FirstOrDefault(); // 精确到秒 + var nonce = Request.Headers["nonce"].FirstOrDefault(); + var sign = Request.Headers["sign"].FirstOrDefault(); + + if (string.IsNullOrEmpty(accessKey)) + return await AuthenticateResultFailAsync("accessKey 不能为空"); + if (string.IsNullOrEmpty(timestampStr)) + return await AuthenticateResultFailAsync("timestamp 不能为空"); + if (string.IsNullOrEmpty(nonce)) + return await AuthenticateResultFailAsync("nonce 不能为空"); + if (string.IsNullOrEmpty(sign)) + return await AuthenticateResultFailAsync("sign 不能为空"); + + // 验证请求数据是否在可接受的时间内 + if (!long.TryParse(timestampStr, out var timestamp)) + return await AuthenticateResultFailAsync("timestamp 值不合法"); + + var requestDate = DateTimeUtil.ToLocalTimeDateBySeconds(timestamp); + +#if NET6_0 + var utcNow = Clock.UtcNow; +#else + var utcNow = TimeProvider.GetUtcNow(); +#endif + if (requestDate > utcNow.Add(Options.AllowedDateDrift).LocalDateTime || requestDate < utcNow.Subtract(Options.AllowedDateDrift).LocalDateTime) + return await AuthenticateResultFailAsync("timestamp 值已超过允许的偏差范围"); + + // 获取 accessSecret + var getAccessSecretContext = new GetAccessSecretContext(Context, Scheme, Options) { AccessKey = accessKey }; + var accessSecret = await Events.GetAccessSecret(getAccessSecretContext); + if (string.IsNullOrEmpty(accessSecret)) + return await AuthenticateResultFailAsync("accessKey 无效"); + + // 校验签名 + var appSecretByte = Encoding.UTF8.GetBytes(accessSecret); + string serverSign = SignData(appSecretByte, GetMessageForSign(Context)); + + if (serverSign != sign) + return await AuthenticateResultFailAsync("sign 无效的签名"); + + // 重放检测 + var cache = App.GetRequiredService(); + var cacheKey = $"{CacheConst.KeyOpenAccessNonce}{accessKey}|{nonce}"; + if (cache.ExistKey(cacheKey)) + return await AuthenticateResultFailAsync("重复的请求"); + cache.Set(cacheKey, null, Options.AllowedDateDrift * 2); // 缓存过期时间为偏差范围时间的2倍 + + // 已验证成功 + var signatureValidatedContext = new SignatureValidatedContext(Context, Scheme, Options) + { + Principal = new ClaimsPrincipal(new ClaimsIdentity(SignatureAuthenticationDefaults.AuthenticationScheme)), + AccessKey = accessKey + }; + await Events.Validated(signatureValidatedContext); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (signatureValidatedContext.Result != null) + return signatureValidatedContext.Result; + + // ReSharper disable once HeuristicUnreachableCode + signatureValidatedContext.Success(); + return signatureValidatedContext.Result; + } + + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + var authResult = await HandleAuthenticateOnceSafeAsync(); + var challengeContext = new SignatureChallengeContext(Context, Scheme, Options, properties) + { + AuthenticateFailure = authResult.Failure, + }; + await Events.Challenge(challengeContext); + // 质询已处理 + if (challengeContext.Handled) return; + + await base.HandleChallengeAsync(properties); + } + + /// + /// 获取用于签名的消息 + /// + /// + private static string GetMessageForSign(HttpContext context) + { + var method = context.Request.Method; // 请求方法(大写) + var url = context.Request.Path; // 请求 url,去除协议、域名、参数,以 / 开头 + var accessKey = context.Request.Headers["accessKey"].FirstOrDefault(); // 身份标识 + var timestamp = context.Request.Headers["timestamp"].FirstOrDefault(); // 时间戳,精确到秒 + var nonce = context.Request.Headers["nonce"].FirstOrDefault(); // 唯一随机数 + + return $"{method}&{url}&{accessKey}&{timestamp}&{nonce}"; + } + + /// + /// 对数据进行签名 + /// + /// + /// + /// + private static string SignData(byte[] secret, string data) + { + if (secret == null) + throw new ArgumentNullException(nameof(secret)); + + if (data == null) + throw new ArgumentNullException(nameof(data)); + + using HMAC hmac = new HMACSHA256(); + hmac.Key = secret; + return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))); + } + + /// + /// 返回验证失败结果,并在 Items 中增加 ,记录身份验证失败消息 + /// + /// + /// + private Task AuthenticateResultFailAsync(string message) + { + // 写入身份验证失败消息 + Context.Items[SignatureAuthenticationDefaults.AuthenticateFailMsgKey] = message; + return Task.FromResult(AuthenticateResult.Fail(message)); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationOptions.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationOptions.cs new file mode 100644 index 00000000..9b91ba2f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationOptions.cs @@ -0,0 +1,29 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证选项 +/// +public class SignatureAuthenticationOptions : AuthenticationSchemeOptions +{ + /// + /// 请求时间允许的偏差范围 + /// + public TimeSpan AllowedDateDrift { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// Signature 身份验证事件 + /// + public new SignatureAuthenticationEvent Events + { + get => (SignatureAuthenticationEvent)base.Events; + set => base.Events = value; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureChallengeContext.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureChallengeContext.cs new file mode 100644 index 00000000..a975bd40 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureChallengeContext.cs @@ -0,0 +1,33 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证质询上下文 +/// +public class SignatureChallengeContext : PropertiesContext +{ + public SignatureChallengeContext(HttpContext context, + AuthenticationScheme scheme, + SignatureAuthenticationOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) + { + } + + /// + /// 在认证期间出现的异常 + /// + public Exception AuthenticateFailure { get; set; } + + /// + /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑 + /// + public bool Handled { get; private set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureValidatedContext.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureValidatedContext.cs new file mode 100644 index 00000000..7bad7e3d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureValidatedContext.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +/// +/// Signature 身份验证已验证上下文 +/// +public class SignatureValidatedContext : ResultContext +{ + public SignatureValidatedContext(HttpContext context, + AuthenticationScheme scheme, + SignatureAuthenticationOptions options) + : base(context, scheme, options) + { + } + + /// + /// 身份标识 + /// + public string AccessKey { get; set; } + + /// + /// 密钥 + /// + public string AccessSecret { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarEntitySeedData.cs b/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarEntitySeedData.cs new file mode 100644 index 00000000..bad2faa5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarEntitySeedData.cs @@ -0,0 +1,21 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 实体种子数据接口 +/// +/// +public interface ISqlSugarEntitySeedData + where TEntity : class, new() +{ + /// + /// 种子数据 + /// + /// + IEnumerable HasData(); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarRepository.cs b/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarRepository.cs new file mode 100644 index 00000000..63fae4fc --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarRepository.cs @@ -0,0 +1,91 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 分表操作仓储接口 +/// +/// +public interface ISqlSugarRepository : ISugarRepository, ISimpleClient where T : class, new() +{ + /// + /// 创建数据 + /// + /// + /// + Task SplitTableInsertAsync(T input); + + /// + /// 批量创建数据 + /// + /// + /// + Task SplitTableInsertAsync(List input); + + /// + /// 更新数据 + /// + /// + /// + Task SplitTableUpdateAsync(T input); + + /// + /// 批量更新数据 + /// + /// + /// + Task SplitTableUpdateAsync(List input); + + /// + /// 删除数据 + /// + /// + /// + Task SplitTableDeleteableAsync(T input); + + /// + /// 批量删除数据 + /// + /// + /// + Task SplitTableDeleteableAsync(List input); + + /// + /// 获取第一条 + /// + /// + /// + Task SplitTableGetFirstAsync(Expression> whereExpression); + + /// + /// 判断是否存在 + /// + /// + /// + Task SplitTableIsAnyAsync(Expression> whereExpression); + + /// + /// 获取列表 + /// + /// + Task> SplitTableGetListAsync(); + + /// + /// 获取列表 + /// + /// + /// + Task> SplitTableGetListAsync(Expression> whereExpression); + + /// + /// 获取列表 + /// + /// + /// 表名 + /// + Task> SplitTableGetListAsync(Expression> whereExpression, string[] tableNames); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs new file mode 100644 index 00000000..05a9b022 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs @@ -0,0 +1,210 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class SqlSugarFilter +{ + /// + /// 缓存全局查询过滤器(内存缓存) + /// + private static readonly ICache _cache = Cache.Default; + + /// + /// 删除用户机构缓存 + /// + /// + /// + public static void DeleteUserOrgCache(long userId, string dbConfigId) + { + var sysCacheService = App.GetRequiredService(); + + // 删除用户机构集合缓存 + sysCacheService.Remove($"{CacheConst.KeyUserOrg}{userId}"); + // 删除最大数据权限缓存 + sysCacheService.Remove($"{CacheConst.KeyRoleMaxDataScope}{userId}"); + // 删除用户机构(数据范围)缓存——过滤器 + _cache.Remove($"db:{dbConfigId}:orgList:{userId}"); + } + + /// + /// 配置用户机构集合过滤器 + /// + public static void SetOrgEntityFilter(SqlSugarScopeProvider db) + { + // 若仅本人数据,则直接返回 + if (SetDataScopeFilter(db) == (int)DataScopeEnum.Self) return; + + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + if (string.IsNullOrWhiteSpace(userId)) return; + + // 配置用户机构集合缓存 + var cacheKey = $"db:{db.CurrentConnectionConfig.ConfigId}:orgList:{userId}"; + var orgFilter = _cache.Get>(cacheKey); + if (orgFilter == null) + { + // 获取用户所属机构,保证同一作用域 + var orgIds = new List(); + Scoped.Create((factory, scope) => + { + var services = scope.ServiceProvider; + orgIds = services.GetService().GetUserOrgIdList().GetAwaiter().GetResult(); + }); + if (orgIds == null || orgIds.Count == 0) return; + + // 获取业务实体数据表 + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass + && u.IsSubclassOf(typeof(EntityBaseData))); + if (!entityTypes.Any()) return; + + orgFilter = new ConcurrentDictionary(); + foreach (var entityType in entityTypes) + { + // 排除非当前数据库实体 + var tAtt = entityType.GetCustomAttribute(); + if ((tAtt != null && db.CurrentConnectionConfig.ConfigId.ToString() != tAtt.configId.ToString())) + continue; + + var lambda = DynamicExpressionParser.ParseLambda(new[] { + Expression.Parameter(entityType, "u") }, typeof(bool), $"@0.Contains(u.{nameof(EntityBaseData.CreateOrgId)}??{default(long)})", orgIds); + db.QueryFilter.AddTableFilter(entityType, lambda); + orgFilter.TryAdd(entityType, lambda); + } + _cache.Add(cacheKey, orgFilter); + } + else + { + foreach (var filter in orgFilter) + db.QueryFilter.AddTableFilter(filter.Key, filter.Value); + } + } + + /// + /// 配置用户仅本人数据过滤器 + /// + private static int SetDataScopeFilter(SqlSugarScopeProvider db) + { + var maxDataScope = (int)DataScopeEnum.All; + + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + if (string.IsNullOrWhiteSpace(userId)) return maxDataScope; + + // 获取用户最大数据范围---仅本人数据 + maxDataScope = App.GetRequiredService().Get(CacheConst.KeyRoleMaxDataScope + userId); + if (maxDataScope != (int)DataScopeEnum.Self) return maxDataScope; + + // 配置用户数据范围缓存 + var cacheKey = $"db:{db.CurrentConnectionConfig.ConfigId}:dataScope:{userId}"; + var dataScopeFilter = _cache.Get>(cacheKey); + if (dataScopeFilter == null) + { + // 获取业务实体数据表 + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass + && u.IsSubclassOf(typeof(EntityBaseData))); + if (!entityTypes.Any()) return maxDataScope; + + dataScopeFilter = new ConcurrentDictionary(); + foreach (var entityType in entityTypes) + { + // 排除非当前数据库实体 + var tAtt = entityType.GetCustomAttribute(); + if ((tAtt != null && db.CurrentConnectionConfig.ConfigId.ToString() != tAtt.configId.ToString())) + continue; + + var lambda = DynamicExpressionParser.ParseLambda(new[] { + Expression.Parameter(entityType, "u") }, typeof(bool), $"u.{nameof(EntityBaseData.CreateUserId)}=@0", userId); + db.QueryFilter.AddTableFilter(entityType, lambda); + dataScopeFilter.TryAdd(entityType, lambda); + } + _cache.Add(cacheKey, dataScopeFilter); + } + else + { + foreach (var filter in dataScopeFilter) + db.QueryFilter.AddTableFilter(filter.Key, filter.Value); + } + return maxDataScope; + } + + /// + /// 配置自定义过滤器 + /// + public static void SetCustomEntityFilter(SqlSugarScopeProvider db) + { + // 配置自定义缓存 + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + var cacheKey = $"db:{db.CurrentConnectionConfig.ConfigId}:custom:{userId}"; + var tableFilterItemList = _cache.Get>>(cacheKey); + if (tableFilterItemList == null) + { + // 获取自定义实体过滤器 + var entityFilterTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass + && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(IEntityFilter)))); + if (!entityFilterTypes.Any()) return; + + var tableFilterItems = new List>(); + foreach (var entityFilter in entityFilterTypes) + { + var instance = Activator.CreateInstance(entityFilter); + var entityFilterMethod = entityFilter.GetMethod("AddEntityFilter"); + var entityFilters = ((IList)entityFilterMethod?.Invoke(instance, null))?.Cast(); + if (entityFilters == null) continue; + + foreach (var u in entityFilters) + { + var tableFilterItem = (TableFilterItem)u; + var entityType = tableFilterItem.GetType().GetProperty("type", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(tableFilterItem, null) as Type; + // 排除非当前数据库实体 + var tAtt = entityType.GetCustomAttribute(); + if ((tAtt != null && db.CurrentConnectionConfig.ConfigId.ToString() != tAtt.configId.ToString()) || + (tAtt == null && db.CurrentConnectionConfig.ConfigId.ToString() != SqlSugarConst.MainConfigId)) + continue; + + tableFilterItems.Add(tableFilterItem); + db.QueryFilter.Add(tableFilterItem); + } + } + _cache.Add(cacheKey, tableFilterItems); + } + else + { + tableFilterItemList.ForEach(u => + { + db.QueryFilter.Add(u); + }); + } + } +} + +/// +/// 自定义实体过滤器接口 +/// +public interface IEntityFilter +{ + /// + /// 实体过滤器 + /// + /// + IEnumerable> AddEntityFilter(); +} + +///// +///// 自定义业务实体过滤器示例 +///// +//public class TestEntityFilter : IEntityFilter +//{ +// public IEnumerable> AddEntityFilter() +// { +// // 构造自定义条件的过滤器 +// Expression> dynamicExpression = u => u.Remark.Contains("xxx"); +// var tableFilterItem = new TableFilterItem(typeof(SysUser), dynamicExpression); + +// return new[] +// { +// tableFilterItem +// }; +// } +//} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarPagedList.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarPagedList.cs new file mode 100644 index 00000000..c53ddc58 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarPagedList.cs @@ -0,0 +1,153 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 分页泛型集合 +/// +/// +public class SqlSugarPagedList +{ + /// + /// 页码 + /// + public int Page { get; set; } + + /// + /// 页容量 + /// + public int PageSize { get; set; } + + /// + /// 总条数 + /// + public int Total { get; set; } + + /// + /// 总页数 + /// + public int TotalPages { get; set; } + + /// + /// 当前页集合 + /// + public IEnumerable Items { get; set; } + + /// + /// 是否有上一页 + /// + public bool HasPrevPage { get; set; } + + /// + /// 是否有下一页 + /// + public bool HasNextPage { get; set; } +} + +/// +/// 分页拓展类 +/// +public static class SqlSugarPagedExtensions +{ + /// + /// 分页拓展 + /// + /// 对象 + /// 当前页码,从1开始 + /// 页码容量 + /// 查询结果 Select 表达式 + /// + public static SqlSugarPagedList ToPagedList(this ISugarQueryable query, int pageIndex, int pageSize, + Expression> expression) + { + var total = 0; + var items = query.ToPageList(pageIndex, pageSize, ref total, expression); + return CreateSqlSugarPagedList(items, total, pageIndex, pageSize); + } + + /// + /// 分页拓展 + /// + /// 对象 + /// 当前页码,从1开始 + /// 页码容量 + /// + public static SqlSugarPagedList ToPagedList(this ISugarQueryable query, int pageIndex, int pageSize) + { + var total = 0; + var items = query.ToPageList(pageIndex, pageSize, ref total); + return CreateSqlSugarPagedList(items, total, pageIndex, pageSize); + } + + /// + /// 分页拓展 + /// + /// 对象 + /// 当前页码,从1开始 + /// 页码容量 + /// 查询结果 Select 表达式 + /// + public static async Task> ToPagedListAsync(this ISugarQueryable query, int pageIndex, int pageSize, + Expression> expression) + { + RefAsync total = 0; + var items = await query.ToPageListAsync(pageIndex, pageSize, total, expression); + return CreateSqlSugarPagedList(items, total, pageIndex, pageSize); + } + + /// + /// 分页拓展 + /// + /// 对象 + /// 当前页码,从1开始 + /// 页码容量 + /// + public static async Task> ToPagedListAsync(this ISugarQueryable query, int pageIndex, int pageSize) + { + RefAsync total = 0; + var items = await query.ToPageListAsync(pageIndex, pageSize, total); + return CreateSqlSugarPagedList(items, total, pageIndex, pageSize); + } + + /// + /// 分页拓展 + /// + /// 集合对象 + /// 当前页码,从1开始 + /// 页码容量 + /// + public static SqlSugarPagedList ToPagedList(this IEnumerable list, int pageIndex, int pageSize) + { + var total = list.Count(); + var items = list.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); + return CreateSqlSugarPagedList(items, total, pageIndex, pageSize); + } + + /// + /// 创建 对象 + /// + /// + /// 分页内容的对象集合 + /// 总条数 + /// 当前页码,从1开始 + /// 页码容量 + /// + private static SqlSugarPagedList CreateSqlSugarPagedList(IEnumerable items, int total, int pageIndex, int pageSize) + { + var totalPages = pageSize > 0 ? (int)Math.Ceiling(total / (double)pageSize) : 0; + return new SqlSugarPagedList + { + Page = pageIndex, + PageSize = pageSize, + Items = items, + Total = total, + TotalPages = totalPages, + HasNextPage = pageIndex < totalPages, + HasPrevPage = pageIndex - 1 > 0 + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarRepository.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarRepository.cs new file mode 100644 index 00000000..a890ef02 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarRepository.cs @@ -0,0 +1,107 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// SqlSugar 实体仓储 +/// +/// +public class SqlSugarRepository : SimpleClient, ISqlSugarRepository where T : class, new() +{ + public SqlSugarRepository() + { + var iTenant = SqlSugarSetup.ITenant; // App.GetRequiredService().AsTenant(); + base.Context = iTenant.GetConnectionScope(SqlSugarConst.MainConfigId); + + // 若实体贴有多库特性,则返回指定库连接 + if (typeof(T).IsDefined(typeof(TenantAttribute), false)) + { + base.Context = iTenant.GetConnectionScopeWithAttr(); + return; + } + + // 若实体贴有日志表特性,则返回日志库连接 + if (typeof(T).IsDefined(typeof(LogTableAttribute), false)) + { + if (iTenant.IsAnyConnection(SqlSugarConst.LogConfigId)) + base.Context = iTenant.GetConnectionScope(SqlSugarConst.LogConfigId); + return; + } + + // 若实体贴有系统表特性,则返回默认库连接 + if (typeof(T).IsDefined(typeof(SysTableAttribute), false)) + return; + + // 若未贴任何表特性或当前未登录或是默认租户Id,则返回默认库连接 + var tenantId = App.User?.FindFirst(ClaimConst.TenantId)?.Value; + if (string.IsNullOrWhiteSpace(tenantId) || tenantId == SqlSugarConst.MainConfigId) return; + + // 根据租户Id切换库连接, 为空则返回默认库连接 + var sqlSugarScopeProviderTenant = App.GetRequiredService().GetTenantDbConnectionScope(long.Parse(tenantId)); + if (sqlSugarScopeProviderTenant == null) return; + base.Context = sqlSugarScopeProviderTenant; + } + + #region 分表操作 + + public async Task SplitTableInsertAsync(T input) + { + return await base.AsInsertable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public async Task SplitTableInsertAsync(List input) + { + return await base.AsInsertable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public async Task SplitTableUpdateAsync(T input) + { + return await base.AsUpdateable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public async Task SplitTableUpdateAsync(List input) + { + return await base.AsUpdateable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public async Task SplitTableDeleteableAsync(T input) + { + return await base.Context.Deleteable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public async Task SplitTableDeleteableAsync(List input) + { + return await base.Context.Deleteable(input).SplitTable().ExecuteCommandAsync() > 0; + } + + public Task SplitTableGetFirstAsync(Expression> whereExpression) + { + return base.AsQueryable().SplitTable().FirstAsync(whereExpression); + } + + public Task SplitTableIsAnyAsync(Expression> whereExpression) + { + return base.Context.Queryable().Where(whereExpression).SplitTable().AnyAsync(); + } + + public Task> SplitTableGetListAsync() + { + return Context.Queryable().SplitTable().ToListAsync(); + } + + public Task> SplitTableGetListAsync(Expression> whereExpression) + { + return Context.Queryable().Where(whereExpression).SplitTable().ToListAsync(); + } + + public Task> SplitTableGetListAsync(Expression> whereExpression, string[] tableNames) + { + return Context.Queryable().Where(whereExpression).SplitTable(t => t.InTableNames(tableNames)).ToListAsync(); + } + + #endregion 分表操作 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs new file mode 100644 index 00000000..05612ba1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs @@ -0,0 +1,458 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class SqlSugarSetup +{ + // 多租户实例 + public static ITenant ITenant { get; set; } + + /// + /// SqlSugar 上下文初始化 + /// + /// + public static void AddSqlSugar(this IServiceCollection services) + { + // 注册雪花Id + var snowIdOpt = App.GetConfig("SnowId", true); + YitIdHelper.SetIdGenerator(snowIdOpt); + + // 自定义 SqlSugar 雪花ID算法 + SnowFlakeSingle.WorkId = snowIdOpt.WorkerId; + StaticConfig.CustomSnowFlakeFunc = () => + { + return YitIdHelper.NextId(); + }; + + var dbOptions = App.GetConfig("DbConnection", true); + dbOptions.ConnectionConfigs.ForEach(SetDbConfig); + + SqlSugarScope sqlSugar = new(dbOptions.ConnectionConfigs.Adapt>(), db => + { + dbOptions.ConnectionConfigs.ForEach(config => + { + var dbProvider = db.GetConnectionScope(config.ConfigId); + SetDbAop(dbProvider, dbOptions.EnableConsoleSql); + SetDbDiffLog(dbProvider, config); + }); + }); + ITenant = sqlSugar; + + services.AddSingleton(sqlSugar); // 单例注册 + services.AddScoped(typeof(SqlSugarRepository<>)); // 仓储注册 + services.AddUnitOfWork(); // 事务与工作单元注册 + + // 初始化数据库表结构及种子数据 + dbOptions.ConnectionConfigs.ForEach(config => + { + InitDatabase(sqlSugar, config); + }); + } + + /// + /// 配置连接属性 + /// + /// + public static void SetDbConfig(DbConnectionConfig config) + { + var configureExternalServices = new ConfigureExternalServices + { + EntityNameService = (type, entity) => // 处理表 + { + entity.IsDisabledDelete = true; // 禁止删除非 sqlsugar 创建的列 + // 只处理贴了特性[SugarTable]表 + if (!type.GetCustomAttributes().Any()) + return; + if (config.DbSettings.EnableUnderLine && !entity.DbTableName.Contains('_')) + entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线 + }, + EntityService = (type, column) => // 处理列 + { + // 只处理贴了特性[SugarColumn]列 + if (!type.GetCustomAttributes().Any()) + return; + if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable) + column.IsNullable = true; + if (config.DbSettings.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_')) + column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); // 驼峰转下划线 + }, + DataInfoCacheService = new SqlSugarCache(), + }; + config.ConfigureExternalServices = configureExternalServices; + config.InitKeyType = InitKeyType.Attribute; + config.IsAutoCloseConnection = true; + config.MoreSettings = new ConnMoreSettings + { + IsAutoRemoveDataCache = true, // 启用自动删除缓存,所有增删改会自动调用.RemoveDataCache() + IsAutoDeleteQueryFilter = true, // 启用删除查询过滤器 + IsAutoUpdateQueryFilter = true, // 启用更新查询过滤器 + SqlServerCodeFirstNvarchar = true // 采用Nvarchar + }; + } + + /// + /// 配置Aop + /// + /// + /// + public static void SetDbAop(SqlSugarScopeProvider db, bool enableConsoleSql) + { + // 设置超时时间 + db.Ado.CommandTimeOut = 30; + + // 打印SQL语句 + if (enableConsoleSql) + { + db.Aop.OnLogExecuting = (sql, pars) => + { + //// 若参数值超过100个字符则进行截取 + //foreach (var par in pars) + //{ + // if (par.DbType != System.Data.DbType.String || par.Value == null) continue; + // if (par.Value.ToString().Length > 100) + // par.Value = string.Concat(par.Value.ToString()[..100], "......"); + //} + + var log = $"【{DateTime.Now}——执行SQL】\r\n{UtilMethods.GetNativeSql(sql, pars)}\r\n"; + var originColor = Console.ForegroundColor; + if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) + Console.ForegroundColor = ConsoleColor.Green; + if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase)) + Console.ForegroundColor = ConsoleColor.Yellow; + if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase)) + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(log); + Console.ForegroundColor = originColor; + App.PrintToMiniProfiler("SqlSugar", "Info", log); + }; + db.Aop.OnError = ex => + { + if (ex.Parametres == null) return; + var log = $"【{DateTime.Now}——错误SQL】\r\n{UtilMethods.GetNativeSql(ex.Sql, (SugarParameter[])ex.Parametres)}\r\n"; + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine(log); + Console.ForegroundColor = originColor; + App.PrintToMiniProfiler("SqlSugar", "Error", log); + }; + db.Aop.OnLogExecuted = (sql, pars) => + { + //// 若参数值超过100个字符则进行截取 + //foreach (var par in pars) + //{ + // if (par.DbType != System.Data.DbType.String || par.Value == null) continue; + // if (par.Value.ToString().Length > 100) + // par.Value = string.Concat(par.Value.ToString()[..100], "......"); + //} + + // 执行时间超过5秒时 + if (db.Ado.SqlExecutionTime.TotalSeconds > 5) + { + var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名 + var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号 + var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名 + var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】:{fileName}\r\n【代码行数】:{fileLine}\r\n【方法名】:{firstMethodName}\r\n" + $"【SQL语句】:{UtilMethods.GetNativeSql(sql, pars)}"; + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine(log); + Console.ForegroundColor = originColor; + App.PrintToMiniProfiler("SqlSugar", "Slow", log); + } + }; + } + // 数据审计 + db.Aop.DataExecuting = (oldValue, entityInfo) => + { + // 新增/插入 + if (entityInfo.OperationType == DataFilterType.InsertByObject) + { + // 若主键是长整型且空则赋值雪花Id + if (entityInfo.EntityColumnInfo.IsPrimarykey && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long)) + { + var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue); + if (id == null || (long)id == 0) + entityInfo.SetValue(YitIdHelper.NextId()); + } + // 若创建时间为空则赋值当前时间 + else if (entityInfo.PropertyName == nameof(EntityBase.CreateTime) && entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue) == null) + { + entityInfo.SetValue(DateTime.Now); + } + // 若当前用户非空(web线程时) + if (App.User != null) + { + if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId)) + { + var tenantId = ((dynamic)entityInfo.EntityValue).TenantId; + if (tenantId == null || tenantId == 0) + entityInfo.SetValue(App.User.FindFirst(ClaimConst.TenantId)?.Value); + } + else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserId)) + { + var createUserId = ((dynamic)entityInfo.EntityValue).CreateUserId; + if (createUserId == 0 || createUserId == null) + entityInfo.SetValue(App.User.FindFirst(ClaimConst.UserId)?.Value); + } + else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserName)) + { + var createUserName = ((dynamic)entityInfo.EntityValue).CreateUserName; + if (string.IsNullOrEmpty(createUserName)) + entityInfo.SetValue(App.User.FindFirst(ClaimConst.RealName)?.Value); + } + else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgId)) + { + var createOrgId = ((dynamic)entityInfo.EntityValue).CreateOrgId; + if (createOrgId == 0 || createOrgId == null) + entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgId)?.Value); + } + else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgName)) + { + var createOrgName = ((dynamic)entityInfo.EntityValue).CreateOrgName; + if (string.IsNullOrEmpty(createOrgName)) + entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgName)?.Value); + } + } + } + // 编辑/更新 + else if (entityInfo.OperationType == DataFilterType.UpdateByObject) + { + if (entityInfo.PropertyName == nameof(EntityBase.UpdateTime)) + entityInfo.SetValue(DateTime.Now); + else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserId)) + entityInfo.SetValue(App.User?.FindFirst(ClaimConst.UserId)?.Value); + else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserName)) + entityInfo.SetValue(App.User?.FindFirst(ClaimConst.RealName)?.Value); + } + }; + + // 超管排除其他过滤器 + if (App.User?.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString()) + return; + + // 配置假删除过滤器 + db.QueryFilter.AddTableFilter(u => u.IsDelete == false); + + // 配置租户过滤器 + var tenantId = App.User?.FindFirst(ClaimConst.TenantId)?.Value; + if (!string.IsNullOrWhiteSpace(tenantId)) + db.QueryFilter.AddTableFilter(u => u.TenantId == long.Parse(tenantId)); + + // 配置用户机构(数据范围)过滤器 + SqlSugarFilter.SetOrgEntityFilter(db); + + // 配置自定义过滤器 + SqlSugarFilter.SetCustomEntityFilter(db); + } + + /// + /// 开启库表差异化日志 + /// + /// + /// + private static void SetDbDiffLog(SqlSugarScopeProvider db, DbConnectionConfig config) + { + if (!config.DbSettings.EnableDiffLog) return; + + db.Aop.OnDiffLogEvent = async u => + { + var logDiff = new SysLogDiff + { + // 操作后记录(字段描述、列名、值、表名、表描述) + AfterData = JSON.Serialize(u.AfterData), + // 操作前记录(字段描述、列名、值、表名、表描述) + BeforeData = JSON.Serialize(u.BeforeData), + // 传进来的对象(如果对象为空,则使用首个数据的表名作为业务对象) + BusinessData = u.BusinessData == null ? u.AfterData.FirstOrDefault()?.TableName : JSON.Serialize(u.BusinessData), + // 枚举(insert、update、delete) + DiffType = u.DiffType.ToString(), + Sql = UtilMethods.GetNativeSql(u.Sql, u.Parameters), + Parameters = JSON.Serialize(u.Parameters), + Elapsed = u.Time == null ? 0 : (long)u.Time.Value.TotalMilliseconds + }; + var logDb = ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) ? ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) : db; + await logDb.CopyNew().Insertable(logDiff).ExecuteCommandAsync(); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(DateTime.Now + $"\r\n*****开始差异日志*****\r\n{Environment.NewLine}{JSON.Serialize(logDiff)}{Environment.NewLine}*****结束差异日志*****\r\n"); + }; + } + + /// + /// 初始化数据库 + /// + /// + /// + private static void InitDatabase(SqlSugarScope db, DbConnectionConfig config) + { + SqlSugarScopeProvider dbProvider = db.GetConnectionScope(config.ConfigId); + + // 初始化/创建数据库 + if (config.DbSettings.EnableInitDb) + { + if (config.DbType != SqlSugar.DbType.Oracle) + dbProvider.DbMaintenance.CreateDatabase(); + } + + // 初始化表结构 + if (config.TableSettings.EnableInitTable) + { + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)) + .Where(u => !u.GetCustomAttributes().Any()) + .WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)).ToList(); + + if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性) + entityTypes = entityTypes.Where(u => u.GetCustomAttributes().Any() || (!u.GetCustomAttributes().Any() && !u.GetCustomAttributes().Any())).ToList(); + else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库 + entityTypes = entityTypes.Where(u => u.GetCustomAttributes().Any()).ToList(); + else + entityTypes = entityTypes.Where(u => u.GetCustomAttribute()?.configId.ToString() == config.ConfigId.ToString()).ToList(); // 自定义的库 + + foreach (var entityType in entityTypes) + { + if (entityType.GetCustomAttribute() == null) + dbProvider.CodeFirst.InitTables(entityType); + else + dbProvider.CodeFirst.SplitTables().InitTables(entityType); + } + } + + // 初始化种子数据 + if (config.SeedSettings.EnableInitSeed) + { + var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)))) + .WhereIF(config.SeedSettings.EnableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false)).ToList(); + + foreach (var seedType in seedDataTypes) + { + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性) + { + if (entityType.GetCustomAttribute() == null && (entityType.GetCustomAttribute() != null || entityType.GetCustomAttribute() != null)) + continue; + } + else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库 + { + if (entityType.GetCustomAttribute() == null) + continue; + } + else + { + var att = entityType.GetCustomAttribute(); // 自定义的库 + if (att == null || att.configId.ToString() != config.ConfigId.ToString()) continue; + } + + var instance = Activator.CreateInstance(seedType); + var hasDataMethod = seedType.GetMethod("HasData"); + var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast(); + if (seedData == null) continue; + + var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType); + if (entityInfo.Columns.Any(u => u.IsPrimarykey)) + { + // 按主键进行批量增加和更新 + var storage = dbProvider.StorageableByObject(seedData.ToList()).ToStorage(); + storage.AsInsertable.ExecuteCommand(); + if (seedType.GetCustomAttribute() == null) // 有忽略更新种子特性时则不更新 + storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(c => c.PropertyInfo.GetCustomAttribute() != null).Select(c => c.PropertyName).ToArray()).ExecuteCommand(); + } + else + { + // 无主键则只进行插入 + if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any()) + dbProvider.InsertableByObject(seedData.ToList()).ExecuteCommand(); + } + } + } + } + + /// + /// 初始化租户业务数据库 + /// + /// + /// + public static void InitTenantDatabase(ITenant iTenant, DbConnectionConfig config) + { + SetDbConfig(config); + + if (!iTenant.IsAnyConnection(config.ConfigId.ToString())) + iTenant.AddConnection(config); + var db = iTenant.GetConnectionScope(config.ConfigId.ToString()); + db.DbMaintenance.CreateDatabase(); + + // 初始化租户库表结构-获取所有业务应用表(排除系统表、日志表、特定库表) + var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && + !u.IsDefined(typeof(SysTableAttribute), false) && !u.IsDefined(typeof(LogTableAttribute), false) && !u.IsDefined(typeof(TenantAttribute), false)).ToList(); + if (!entityTypes.Any()) return; + + foreach (var entityType in entityTypes) + { + var splitTable = entityType.GetCustomAttribute(); + if (splitTable == null) + db.CodeFirst.InitTables(entityType); + else + db.CodeFirst.SplitTables().InitTables(entityType); + } + + // 初始化业务应用种子数据 + var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)))) + .Where(u => u.IsDefined(typeof(AppSeedAttribute), false)).ToList(); + + foreach (var seedType in seedDataTypes) + { + var instance = Activator.CreateInstance(seedType); + var hasDataMethod = seedType.GetMethod("HasData"); + var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast().ToList(); + if (seedData == null) continue; + + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + var entityInfo = db.EntityMaintenance.GetEntityInfo(entityType); + var dbConfigId = config.ConfigId.ToLong(); + // 若实体包含租户Id字段,则设置为当前租户Id + if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityTenantId.TenantId))) + { + foreach (var sd in seedData) + { + sd.GetType().GetProperty(nameof(EntityTenantId.TenantId)).SetValue(sd, dbConfigId); + } + } + // 若实体包含Pid字段,则设置为当前租户Id + if (entityInfo.Columns.Any(u => u.PropertyName == nameof(SysOrg.Pid))) + { + foreach (var sd in seedData) + { + sd.GetType().GetProperty(nameof(SysOrg.Pid)).SetValue(sd, dbConfigId); + } + } + // 若实体包含Id字段,则设置为当前租户Id递增1 + if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityBaseId.Id))) + { + foreach (var sd in seedData) + { + sd.GetType().GetProperty(nameof(EntityBaseId.Id)).SetValue(sd, ++dbConfigId); + } + } + + // 若实体是系统内置,则切换至默认库 + if (entityType.GetCustomAttribute() != null) + db = iTenant.GetConnectionScope(SqlSugarConst.MainConfigId); + + if (entityInfo.Columns.Any(u => u.IsPrimarykey)) + { + // 按主键进行批量增加和更新 + var storage = db.StorageableByObject(seedData).ToStorage(); + storage.AsInsertable.ExecuteCommand(); + if (seedType.GetCustomAttribute() == null) // 有忽略更新种子特性时则不更新 + storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(c => c.PropertyInfo.GetCustomAttribute() != null).Select(c => c.PropertyName).ToArray()).ExecuteCommand(); + } + else + { + // 无主键则只进行插入 + if (!db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any()) + db.InsertableByObject(seedData).ExecuteCommand(); + } + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarUnitOfWork.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarUnitOfWork.cs new file mode 100644 index 00000000..0975eef6 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarUnitOfWork.cs @@ -0,0 +1,71 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// SqlSugar 事务和工作单元 +/// +public sealed class SqlSugarUnitOfWork : IUnitOfWork +{ + /// + /// SqlSugar 对象 + /// + private readonly ISqlSugarClient _sqlSugarClient; + + /// + /// 构造函数 + /// + /// + public SqlSugarUnitOfWork(ISqlSugarClient sqlSugarClient) + { + _sqlSugarClient = sqlSugarClient; + } + + /// + /// 开启工作单元处理 + /// + /// + /// + /// + public void BeginTransaction(FilterContext context, UnitOfWorkAttribute unitOfWork) + { + _sqlSugarClient.AsTenant().BeginTran(); + } + + /// + /// 提交工作单元处理 + /// + /// + /// + /// + public void CommitTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork) + { + _sqlSugarClient.AsTenant().CommitTran(); + } + + /// + /// 回滚工作单元处理 + /// + /// + /// + /// + public void RollbackTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork) + { + _sqlSugarClient.AsTenant().RollbackTran(); + } + + /// + /// 执行完毕(无论成功失败) + /// + /// + /// + /// + public void OnCompleted(FilterContext context, FilterContext resultContext) + { + _sqlSugarClient.Dispose(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs b/Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs new file mode 100644 index 00000000..dc2a8979 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs @@ -0,0 +1,159 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 全局规范化结果 +/// +[UnifyModel(typeof(AdminResult<>))] +public class AdminResultProvider : IUnifyResultProvider +{ + /// + /// JWT 授权异常返回值 + /// + /// + /// + /// + public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata) + { + return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors), UnifyContext.GetSerializerSettings(context)); + } + + /// + /// 异常返回值 + /// + /// + /// + /// + public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) + { + return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors), UnifyContext.GetSerializerSettings(context)); + } + + /// + /// 成功返回值 + /// + /// + /// + /// + public IActionResult OnSucceeded(ActionExecutedContext context, object data) + { + return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data), UnifyContext.GetSerializerSettings(context)); + } + + /// + /// 验证失败返回值 + /// + /// + /// + /// + public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) + { + return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult), UnifyContext.GetSerializerSettings(context)); + } + + /// + /// 特定状态码返回值 + /// + /// + /// + /// + /// + public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings) + { + // 设置响应状态码 + UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings); + + switch (statusCode) + { + // 处理 401 状态码 + case StatusCodes.Status401Unauthorized: + var msg = "401 登录已过期,请重新登录"; + // 若存在身份验证失败消息,则返回消息内容 + if (context.Items.TryGetValue(SignatureAuthenticationDefaults.AuthenticateFailMsgKey, out var authFailMsg)) + msg = authFailMsg + ""; + await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: msg), + App.GetOptions()?.JsonSerializerOptions); + break; + // 处理 403 状态码 + case StatusCodes.Status403Forbidden: + await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 禁止访问,没有权限"), + App.GetOptions()?.JsonSerializerOptions); + break; + + default: break; + } + } + + /// + /// 返回 RESTful 风格结果集 + /// + /// + /// + /// + /// + /// + private static AdminResult RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default) + { + //// 统一返回值脱敏处理 + //if (data?.GetType() == typeof(String)) + //{ + // data = App.GetRequiredService().ReplaceAsync(data.ToString(), '*').GetAwaiter().GetResult(); + //} + //else if (data?.GetType() == typeof(JsonResult)) + //{ + // data = App.GetRequiredService().ReplaceAsync(JSON.Serialize(data), '*').GetAwaiter().GetResult(); + //} + + return new AdminResult + { + Code = statusCode, + Message = errors is null or string ? (errors + "") : JSON.Serialize(errors), + Result = data, + Type = succeeded ? "success" : "error", + Extras = UnifyContext.Take(), + Time = DateTime.Now + }; + } +} + +/// +/// 全局返回结果 +/// +/// +public class AdminResult +{ + /// + /// 状态码 + /// + public int Code { get; set; } + + /// + /// 类型success、warning、error + /// + public string Type { get; set; } + + /// + /// 错误信息 + /// + public string Message { get; set; } + + /// + /// 数据 + /// + public T Result { get; set; } + + /// + /// 附加数据 + /// + public object Extras { get; set; } + + /// + /// 时间 + /// + public DateTime Time { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/BaseIdInput.cs b/Admin.NET/Admin.NET.Core/Util/BaseIdInput.cs new file mode 100644 index 00000000..bdc86b25 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/BaseIdInput.cs @@ -0,0 +1,20 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 主键Id输入参数 +/// +public class BaseIdInput +{ + /// + /// 主键Id + /// + [Required(ErrorMessage = "Id不能为空")] + [DataValidation(ValidationTypes.Numeric)] + public virtual long Id { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs b/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs new file mode 100644 index 00000000..88203f6a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs @@ -0,0 +1,41 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 全局分页查询输入参数 +/// +public class BasePageInput +{ + /// + /// 当前页码 + /// + [DataValidation(ValidationTypes.Numeric)] + public virtual int Page { get; set; } = 1; + + /// + /// 页码容量 + /// + //[Range(0, 100, ErrorMessage = "页码容量超过最大限制")] + [DataValidation(ValidationTypes.Numeric)] + public virtual int PageSize { get; set; } = 20; + + /// + /// 排序字段 + /// + public virtual string Field { get; set; } + + /// + /// 排序方向 + /// + public virtual string Order { get; set; } + + /// + /// 降序排序 + /// + public virtual string DescStr { get; set; } = "descending"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/ChinaDateTimeConverter.cs b/Admin.NET/Admin.NET.Core/Util/ChinaDateTimeConverter.cs new file mode 100644 index 00000000..020a5d3d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/ChinaDateTimeConverter.cs @@ -0,0 +1,64 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Admin.NET.Core; + +/// +/// JSON时间序列化yyyy-MM-dd HH:mm:ss +/// +public class ChinaDateTimeConverter : DateTimeConverterBase +{ + private static readonly IsoDateTimeConverter dtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return dtConverter.ReadJson(reader, objectType, existingValue, serializer); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + dtConverter.WriteJson(writer, value, serializer); + } +} + +/// +/// JSON时间序列化yyyy-MM-dd HH:mm +/// +public class ChinaDateTimeConverterHH : DateTimeConverterBase +{ + private static readonly IsoDateTimeConverter dtConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm" }; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return dtConverter.ReadJson(reader, objectType, existingValue, serializer); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + dtConverter.WriteJson(writer, value, serializer); + } +} + +/// +/// JSON时间序列化yyyy-MM-dd +/// +public class ChinaDateTimeConverterDate : DateTimeConverterBase +{ + private static readonly IsoDateTimeConverter dtConverter = new() { DateTimeFormat = "yyyy-MM-dd" }; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return dtConverter.ReadJson(reader, objectType, existingValue, serializer); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + dtConverter.WriteJson(writer, value, serializer); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/CodeGenUtil.cs b/Admin.NET/Admin.NET.Core/Util/CodeGenUtil.cs new file mode 100644 index 00000000..10ba5666 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/CodeGenUtil.cs @@ -0,0 +1,293 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using DbType = SqlSugar.DbType; + +namespace Admin.NET.Core; + +/// +/// 代码生成帮助类 +/// +public static class CodeGenUtil +{ + /// + /// 转换大驼峰法命名 + /// + /// 字段名 + /// EntityBase 实体属性名称 + /// + public static string CamelColumnName(string columnName, string[] dbColumnNames) + { + if (columnName.Contains('_')) + { + var arrColName = columnName.Split('_'); + var sb = new StringBuilder(); + foreach (var col in arrColName) + { + if (col.Length > 0) + sb.Append(col[..1].ToUpper() + col[1..].ToLower()); + } + columnName = sb.ToString(); + } + else + { + var propertyName = dbColumnNames.FirstOrDefault(c => c.ToLower() == columnName.ToLower()); + if (!string.IsNullOrEmpty(propertyName)) + { + columnName = propertyName; + } + else + { + columnName = columnName[..1].ToUpper() + columnName[1..].ToLower(); + } + } + return columnName; + } + + // 根据数据库类型来处理对应的数据字段类型 + public static string ConvertDataType(DbColumnInfo dbColumnInfo, DbType dbType = DbType.Custom) + { + if (dbType == DbType.Custom) + dbType = App.GetOptions().ConnectionConfigs[0].DbType; + + var dataType = dbType switch + { + DbType.Oracle => ConvertDataType_OracleSQL(string.IsNullOrEmpty(dbColumnInfo.OracleDataType) ? dbColumnInfo.DataType : dbColumnInfo.OracleDataType, dbColumnInfo.Length, dbColumnInfo.Scale), + DbType.PostgreSQL => ConvertDataType_PostgreSQL(dbColumnInfo.DataType), + _ => ConvertDataType_Default(dbColumnInfo.DataType), + }; + return dataType + (dbColumnInfo.IsNullable ? "?" : ""); + } + + public static string ConvertDataType_OracleSQL(string dataType, int? length, int? scale) + { + switch (dataType.ToLower()) + { + case "interval year to month": + return "int"; + + case "interval day to second": + return "TimeSpan"; + + case "smallint": + return "Int16"; + + case "int": + case "integer": + return "int"; + + case "long": + return "long"; + + case "float": + return "float"; + + case "decimal": + return "decimal"; + + case "number": + if (length != null) + { + if (scale != null && scale > 0) + { + return "decimal"; + } + else if ((scale != null && scale == 0) || scale == null) + { + if (length > 1 && length < 12) + { + return "int"; + } + else if (length > 11) + { + return "long"; + } + } + if (length == 1) + { + return "bool"; + } + } + return "decimal"; + + case "char": + case "clob": + case "nclob": + case "nchar": + case "nvarchar": + case "varchar": + case "nvarchar2": + case "varchar2": + case "rowid": + return "string"; + + case "timestamp": + case "timestamp with time zone": + case "timestamptz": + case "timestamp without time zone": + case "date": + case "time": + case "time with time zone": + case "timetz": + case "time without time zone": + return "DateTime"; + + case "bfile": + case "blob": + case "raw": + return "byte[]"; + + default: + return "object"; + } + } + + //PostgreSQL数据类型对应的字段类型 + public static string ConvertDataType_PostgreSQL(string dataType) + { + switch (dataType) + { + case "int2": + case "smallint": + return "Int16"; + + case "int4": + case "integer": + return "int"; + + case "int8": + case "bigint": + return "long"; + + case "float4": + case "real": + return "float"; + + case "float8": + case "double precision": + return "double"; + + case "numeric": + case "decimal": + case "path": + case "point": + case "polygon": + case "interval": + case "lseg": + case "macaddr": + case "money": + return "decimal"; + + case "boolean": + case "bool": + case "box": + case "bytea": + return "bool"; + + case "varchar": + case "character varying": + case "geometry": + case "name": + case "text": + case "char": + case "character": + case "cidr": + case "circle": + case "tsquery": + case "tsvector": + case "txid_snapshot": + case "xml": + case "json": + return "string"; + + case "uuid": + return "Guid"; + + case "timestamp": + case "timestamp with time zone": + case "timestamptz": + case "timestamp without time zone": + case "date": + case "time": + case "time with time zone": + case "timetz": + case "time without time zone": + return "DateTime"; + + case "bit": + case "bit varying": + return "byte[]"; + + case "varbit": + return "byte"; + + default: + return "object"; + } + } + + public static string ConvertDataType_Default(string dataType) + { + return dataType.ToLower() switch + { + "tinytext" or "mediumtext" or "longtext" or "mid" or "text" or "varchar" or "char" or "nvarchar" or "nchar" or "timestamp" => "string", + "int" or "integer" => "int", + "smallint" => "Int16", + //"tinyint" => "byte", + "tinyint" => "bool", // MYSQL + "bigint" => "long", + "bit" => "bool", + "money" or "smallmoney" or "numeric" or "decimal" => "decimal", + "real" => "Single", + "datetime" or "smalldatetime" => "DateTime", + "float" or "double" => "double", + "image" or "binary" or "varbinary" => "byte[]", + "uniqueidentifier" => "Guid", + _ => "object", + }; + } + + /// + /// 数据类型转显示类型 + /// + /// + /// + public static string DataTypeToEff(string dataType) + { + if (string.IsNullOrEmpty(dataType)) return ""; + return dataType?.TrimEnd('?') switch + { + "string" => "Input", + "int" => "InputNumber", + "long" => "Input", + "float" => "Input", + "double" => "Input", + "decimal" => "Input", + "bool" => "Switch", + "Guid" => "Input", + "DateTime" => "DatePicker", + _ => "Input", + }; + } + + // 是否通用字段 + public static bool IsCommonColumn(string columnName) + { + var columnList = new List() + { + nameof(EntityBaseData.CreateOrgId), + nameof(EntityTenant.TenantId), + nameof(EntityBase.CreateTime), + nameof(EntityBase.UpdateTime), + nameof(EntityBase.CreateUserId), + nameof(EntityBase.UpdateUserId), + nameof(EntityBase.CreateUserName), + nameof(EntityBase.UpdateUserName), + nameof(EntityBase.IsDelete) + }; + return columnList.Contains(columnName); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs new file mode 100644 index 00000000..a684a4da --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs @@ -0,0 +1,372 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Magicodes.ExporterAndImporter.Core.Models; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Serialization; + +namespace Admin.NET.Core; + +/// +/// 通用工具类 +/// +public static class CommonUtil +{ + /// + /// 生成百分数 + /// + /// + /// + /// + public static string ExecPercent(decimal PassCount, decimal allCount) + { + string res = ""; + if (allCount > 0) + { + var value = (double)Math.Round(PassCount / allCount * 100, 1); + if (value < 0) + res = Math.Round(value + 5 / Math.Pow(10, 0 + 1), 0, MidpointRounding.AwayFromZero).ToString(); + else + res = Math.Round(value, 0, MidpointRounding.AwayFromZero).ToString(); + } + if (res == "") res = "0"; + return res + "%"; + } + + /// + /// 获取服务地址 + /// + /// + public static string GetLocalhost() + { + string result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}"; + + // 代理模式:获取真正的本机地址 + // X-Original-Host=原始请求 + // X-Forwarded-Server=从哪里转发过来 + if (App.HttpContext.Request.Headers.ContainsKey("Origin")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{App.HttpContext.Request.Headers["Origin"]}"; + else if (App.HttpContext.Request.Headers.ContainsKey("X-Original")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{App.HttpContext.Request.Headers["X-Original"]}"; + else if (App.HttpContext.Request.Headers.ContainsKey("X-Original-Host")) + result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Headers["X-Original-Host"]}"; + return result; + } + + /// + /// 对象序列化XML + /// + /// + /// + /// + public static string SerializeObjectToXml(T obj) + { + if (obj == null) return string.Empty; + + var xs = new XmlSerializer(obj.GetType()); + var stream = new MemoryStream(); + var setting = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), // 不包含BOM + Indent = true // 设置格式化缩进 + }; + using (var writer = XmlWriter.Create(stream, setting)) + { + var ns = new XmlSerializerNamespaces(); + ns.Add("", ""); // 去除默认命名空间 + xs.Serialize(writer, obj, ns); + } + return Encoding.UTF8.GetString(stream.ToArray()); + } + + /// + /// 字符串转XML格式 + /// + /// + /// + public static XElement SerializeStringToXml(string xmlStr) + { + try + { + return XElement.Parse(xmlStr); + } + catch + { + return null; + } + } + + /// + /// 导出模板Excel + /// + /// + public static async Task ExportExcelTemplate() where T : class, new() + { + IImporter importer = new ExcelImporter(); + var res = await importer.GenerateTemplateBytes(); + + return new FileContentResult(res, "application/octet-stream") { FileDownloadName = typeof(T).Name + ".xlsx" }; + } + + /// + /// 导出数据excel + /// + /// + public static async Task ExportExcelData(ICollection data) where T : class, new() + { + var export = new ExcelExporter(); + var res = await export.ExportAsByteArray(data); + + return new FileContentResult(res, "application/octet-stream") { FileDownloadName = typeof(T).Name + ".xlsx" }; + } + + /// + /// 导出数据excel,包括字典转换 + /// + /// + public static async Task ExportExcelData(ISugarQueryable query, Func action = null) + where TSource : class, new() where TTarget : class, new() + { + var PropMappings = GetExportPropertMap(); + var data = query.ToList(); + //相同属性复制值,字典值转换 + var result = new List(); + foreach (var item in data) + { + var newData = new TTarget(); + foreach (var dict in PropMappings) + { + var targeProp = dict.Value.Item3; + if (targeProp != null) + { + var propertyInfo = dict.Value.Item2; + var sourceVal = propertyInfo.GetValue(item, null); + if (sourceVal == null) + { + continue; + } + + var map = dict.Value.Item1; + if (map != null && map.ContainsKey(sourceVal)) + { + var newVal = map[sourceVal]; + targeProp.SetValue(newData, newVal); + } + else + { + if (targeProp.PropertyType.FullName == propertyInfo.PropertyType.FullName) + { + targeProp.SetValue(newData, sourceVal); + } + else + { + var newVal = sourceVal.ToString().ParseTo(targeProp.PropertyType); + targeProp.SetValue(newData, newVal); + } + } + } + if (action != null) + { + newData = action(item, newData); + } + } + result.Add(newData); + } + var export = new ExcelExporter(); + var res = await export.ExportAsByteArray(result); + + return new FileContentResult(res, "application/octet-stream") { FileDownloadName = typeof(TTarget).Name + ".xlsx" }; + } + + /// + /// 导入数据Excel + /// + /// + /// + public static async Task> ImportExcelData([Required] IFormFile file) where T : class, new() + { + IImporter importer = new ExcelImporter(); + var res = await importer.Import(file.OpenReadStream()); + var message = string.Empty; + if (res.HasError) + { + if (res.Exception != null) + message += $"\r\n{res.Exception.Message}"; + foreach (DataRowErrorInfo drErrorInfo in res.RowErrors) + { + int rowNum = drErrorInfo.RowIndex; + foreach (var item in drErrorInfo.FieldErrors) + message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; + } + message += "字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + throw Oops.Oh("导入异常:" + message); + } + return res.Data; + } + + // 例:List ls = CommonUtil.ParseList(importResult.Data); + /// + /// 对象转换 含字典转换 + /// + /// + /// + /// + /// + /// + public static List ParseList(IEnumerable data, Func action = null) where TTarget : new() + { + var propMappings = GetImportPropertMap(); + // 相同属性复制值,字典值转换 + var result = new List(); + foreach (var item in data) + { + var newData = new TTarget(); + foreach (var dict in propMappings) + { + var targeProp = dict.Value.Item3; + if (targeProp != null) + { + var propertyInfo = dict.Value.Item2; + var sourceVal = propertyInfo.GetValue(item, null); + if (sourceVal == null) + continue; + + var map = dict.Value.Item1; + if (map != null && map.ContainsKey(sourceVal.ToString())) + { + var newVal = map[sourceVal.ToString()]; + targeProp.SetValue(newData, newVal); + } + else + { + if (targeProp.PropertyType.FullName == propertyInfo.PropertyType.FullName) + { + targeProp.SetValue(newData, sourceVal); + } + else + { + var newVal = sourceVal.ToString().ParseTo(targeProp.PropertyType); + targeProp.SetValue(newData, newVal); + } + } + } + } + if (action != null) + newData = action(item, newData); + + if (newData != null) + result.Add(newData); + } + return result; + } + + /// + /// 获取导入属性映射 + /// + /// + /// + /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 + private static Dictionary, PropertyInfo, PropertyInfo>> GetImportPropertMap() where TTarget : new() + { + // 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息> + var propMappings = new Dictionary, PropertyInfo, PropertyInfo>>(); + + var dictService = App.GetRequiredService>(); + var tSourceProps = typeof(TSource).GetProperties().ToList(); + var tTargetProps = typeof(TTarget).GetProperties().ToDictionary(m => m.Name); + foreach (var propertyInfo in tSourceProps) + { + var attrs = propertyInfo.GetCustomAttribute(); + if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) + { + var targetProp = tTargetProps[attrs.TargetPropName]; + var mappingValues = dictService.Context.Queryable((a, b) => + new JoinQueryInfos(JoinType.Inner, a.Id == b.DictTypeId)) + .Where(a => a.Code == attrs.TypeCode) + .Where((a, b) => a.Status == StatusEnum.Enable && b.Status == StatusEnum.Enable) + .Select((a, b) => new + { + Label = b.Value, + Value = b.Code + }).ToList() + .ToDictionary(m => m.Label, m => m.Value.ParseTo(targetProp.PropertyType)); + propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>(mappingValues, propertyInfo, targetProp)); + } + else + { + propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( + null, propertyInfo, tTargetProps.ContainsKey(propertyInfo.Name) ? tTargetProps[propertyInfo.Name] : null)); + } + } + + return propMappings; + } + + /// + /// 获取导出属性映射 + /// + /// + /// + /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 + private static Dictionary, PropertyInfo, PropertyInfo>> GetExportPropertMap() where TTarget : new() + { + // 整理导入对象的属性名称,<字典数据,原属性信息,目标属性信息> + var propMappings = new Dictionary, PropertyInfo, PropertyInfo>>(); + + var dictService = App.GetRequiredService>(); + var targetProps = typeof(TTarget).GetProperties().ToList(); + var sourceProps = typeof(TSource).GetProperties().ToDictionary(m => m.Name); + foreach (var propertyInfo in targetProps) + { + var attrs = propertyInfo.GetCustomAttribute(); + if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) + { + var targetProp = sourceProps[attrs.TargetPropName]; + var mappingValues = dictService.Context.Queryable((a, b) => + new JoinQueryInfos(JoinType.Inner, a.Id == b.DictTypeId)) + .Where(a => a.Code == attrs.TypeCode) + .Where((a, b) => a.Status == StatusEnum.Enable && b.Status == StatusEnum.Enable) + .Select((a, b) => new + { + Label = b.Value, + Value = b.Code + }).ToList() + .ToDictionary(m => m.Value.ParseTo(targetProp.PropertyType), m => m.Label); + propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>(mappingValues, targetProp, propertyInfo)); + } + else + { + propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( + null, sourceProps.ContainsKey(propertyInfo.Name) ? sourceProps[propertyInfo.Name] : null, propertyInfo)); + } + } + + return propMappings; + } + + /// + /// 获取属性映射 + /// + /// + /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 + private static Dictionary> GetExportDicttMap() where TTarget : new() + { + // 整理导入对象的属性名称,目标属性名,字典Code + var propMappings = new Dictionary>(); + var tTargetProps = typeof(TTarget).GetProperties(); + foreach (var propertyInfo in tTargetProps) + { + var attrs = propertyInfo.GetCustomAttribute(); + if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) + { + propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); + } + } + + return propMappings; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs b/Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs new file mode 100644 index 00000000..60e6940b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs @@ -0,0 +1,480 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class ComputerUtil +{ + /// + /// 内存信息 + /// + /// + public static MemoryMetrics GetComputerInfo() + { + MemoryMetrics memoryMetrics; + if (IsMacOS()) + { + memoryMetrics = MemoryMetricsClient.GetMacOSMetrics(); + } + else if (IsUnix()) + { + memoryMetrics = MemoryMetricsClient.GetUnixMetrics(); + } + else + { + memoryMetrics = MemoryMetricsClient.GetWindowsMetrics(); + } + memoryMetrics.FreeRam = Math.Round(memoryMetrics.Free / 1024, 2) + "GB"; + memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB"; + memoryMetrics.TotalRam = Math.Round(memoryMetrics.Total / 1024, 2) + "GB"; + memoryMetrics.RamRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%"; + memoryMetrics.CpuRate = Math.Ceiling(GetCPURate().ParseToDouble()) + "%"; + return memoryMetrics; + } + + /// + /// 获取正确的操作系统版本(Linux获取发行版本) + /// + /// + public static String GetOSInfo() + { + string opeartion = string.Empty; + if (IsMacOS()) + { + var output = ShellHelper.Bash("sw_vers | awk 'NR<=2{printf \"%s \", $NF}'"); + if (output != null) + { + opeartion = output.Replace("%", string.Empty); + } + } + else if (IsUnix()) + { + var output = ShellHelper.Bash("awk -F= '/^VERSION_ID/ {print $2}' /etc/os-release | tr -d '\"'"); + opeartion = output ?? string.Empty; + } + else + { + opeartion = RuntimeInformation.OSDescription; + } + return opeartion; + } + + /// + /// 磁盘信息 + /// + /// + public static List GetDiskInfos() + { + var diskInfos = new List(); + if (IsMacOS()) + { + var output = ShellHelper.Bash(@"df -m | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$5}'"); + var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); + if (disks.Length < 1) return diskInfos; + foreach (var item in disks) + { + var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); + if (disk == null || disk.Length < 5) + continue; + + var diskInfo = new DiskInfo() + { + DiskName = disk[0], + TypeName = ShellHelper.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty), + TotalSize = long.Parse(disk[1]) / 1024, + Used = long.Parse(disk[2]) / 1024, + AvailableFreeSpace = long.Parse(disk[3]) / 1024, + AvailablePercent = decimal.Parse(disk[4].Replace("%", "")) + }; + diskInfos.Add(diskInfo); + } + } + else if (IsUnix()) + { + var output = ShellHelper.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda)/ {print $1,$2,$3,$4,$5,$6}'"); + var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); + if (disks.Length < 1) return diskInfos; + + //var rootDisk = disks[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); + //if (rootDisk == null || rootDisk.Length < 1) + // return diskInfos; + + foreach (var item in disks) + { + var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); + if (disk == null || disk.Length < 6) + continue; + + var diskInfo = new DiskInfo() + { + DiskName = disk[0], + TypeName = disk[1], + TotalSize = long.Parse(disk[2]) / 1024, + Used = long.Parse(disk[3]) / 1024, + AvailableFreeSpace = long.Parse(disk[4]) / 1024, + AvailablePercent = decimal.Parse(disk[5].Replace("%", "")) + }; + diskInfos.Add(diskInfo); + } + } + else + { + var driv = DriveInfo.GetDrives().Where(u => u.IsReady); + foreach (var item in driv) + { + if (item.DriveType == DriveType.CDRom) continue; + var obj = new DiskInfo() + { + DiskName = item.Name, + TypeName = item.DriveType.ToString(), + TotalSize = item.TotalSize / 1024 / 1024 / 1024, + AvailableFreeSpace = item.AvailableFreeSpace / 1024 / 1024 / 1024, + }; + obj.Used = obj.TotalSize - obj.AvailableFreeSpace; + obj.AvailablePercent = decimal.Ceiling(obj.Used / (decimal)obj.TotalSize * 100); + diskInfos.Add(obj); + } + } + return diskInfos; + } + + /// + /// 获取外网IP地址 + /// + /// + public static string GetIpFromOnline() + { + var url = "https://www.ip.cn/api/index?ip&type=0"; + var str = url.GetAsStringAsync().GetAwaiter().GetResult(); + var resp = JSON.Deserialize(str); + return resp.Ip + " " + resp.Address; + } + + public static bool IsUnix() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + } + + public static bool IsMacOS() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + } + + public static string GetCPURate() + { + string cpuRate; + if (IsMacOS()) + { + string output = ShellUtil.Bash("top -l 1 | grep \"CPU usage\" | awk '{print $3 + $5}'"); + cpuRate = output.Trim(); + } + else if (IsUnix()) + { + string output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'"); + cpuRate = output.Trim(); + } + else + { + string output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage"); + cpuRate = output.Replace("LoadPercentage", string.Empty).Trim(); + } + return cpuRate; + } + + /// + /// 获取系统运行时间 + /// + /// + public static string GetRunTime() + { + string runTime = string.Empty; + if (IsMacOS()) + { + //macOS 获取系统启动时间: + //sysctl -n kern.boottime | awk '{print $4}' | tr -d ',' + //返回:1705379131 + //使用date格式化即可 + string output = ShellUtil.Bash("date -r $(sysctl -n kern.boottime | awk '{print $4}' | tr -d ',') +\"%Y-%m-%d %H:%M:%S\"").Trim(); + runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong()); + } + else if (IsUnix()) + { + string output = ShellUtil.Bash("uptime -s").Trim(); + runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong()); + } + else + { + string output = ShellUtil.Cmd("wmic", "OS get LastBootUpTime/Value"); + string[] outputArr = output.Split('=', (char)StringSplitOptions.RemoveEmptyEntries); + if (outputArr.Length == 2) + runTime = DateTimeUtil.FormatTime((DateTime.Now - outputArr[1].Split('.')[0].ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong()); + } + return runTime; + } +} + +/// +/// IP信息 +/// +public class IpCnResp +{ + public string Ip { get; set; } + + public string Address { get; set; } +} + +/// +/// 内存信息 +/// +public class MemoryMetrics +{ + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public double Total { get; set; } + + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public double Used { get; set; } + + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public double Free { get; set; } + + /// + /// 已用内存 + /// + public string UsedRam { get; set; } + + /// + /// CPU使用率% + /// + public string CpuRate { get; set; } + + /// + /// 总内存 GB + /// + public string TotalRam { get; set; } + + /// + /// 内存使用率 % + /// + public string RamRate { get; set; } + + /// + /// 空闲内存 + /// + public string FreeRam { get; set; } +} + +/// +/// 磁盘信息 +/// +public class DiskInfo +{ + /// + /// 磁盘名 + /// + public string DiskName { get; set; } + + /// + /// 类型名 + /// + public string TypeName { get; set; } + + /// + /// 总剩余 + /// + public long TotalFree { get; set; } + + /// + /// 总量 + /// + public long TotalSize { get; set; } + + /// + /// 已使用 + /// + public long Used { get; set; } + + /// + /// 可使用 + /// + public long AvailableFreeSpace { get; set; } + + /// + /// 使用百分比 + /// + public decimal AvailablePercent { get; set; } +} + +public class MemoryMetricsClient +{ + /// + /// windows系统获取内存信息 + /// + /// + public static MemoryMetrics GetWindowsMetrics() + { + string output = ShellUtil.Cmd("wmic", "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value"); + var metrics = new MemoryMetrics(); + var lines = output.Trim().Split('\n', (char)StringSplitOptions.RemoveEmptyEntries); + if (lines.Length <= 0) return metrics; + + var freeMemoryParts = lines[0].Split('=', (char)StringSplitOptions.RemoveEmptyEntries); + var totalMemoryParts = lines[1].Split('=', (char)StringSplitOptions.RemoveEmptyEntries); + + metrics.Total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 0); + metrics.Free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 0);//m + metrics.Used = metrics.Total - metrics.Free; + + return metrics; + } + + /// + /// Unix系统获取 + /// + /// + public static MemoryMetrics GetUnixMetrics() + { + string output = ShellUtil.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'"); + var metrics = new MemoryMetrics(); + var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries); + if (lines.Length <= 0) return metrics; + + if (lines != null && lines.Length > 0) + { + var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); + if (memory.Length >= 3) + { + metrics.Total = double.Parse(memory[0]); + metrics.Used = double.Parse(memory[1]); + metrics.Free = double.Parse(memory[2]);//m + } + } + return metrics; + } + + /// + /// macOS系统获取 + /// + /// + public static MemoryMetrics GetMacOSMetrics() + { + var metrics = new MemoryMetrics(); + //物理内存大小 + var total = ShellUtil.Bash("sysctl -n hw.memsize | awk '{printf \"%.2f\", $1/1024/1024}'"); + metrics.Total = float.Parse(total.Replace("%", string.Empty)); + //TODO:占用内存,检查效率 + var free = ShellUtil.Bash("top -l 1 -s 0 | awk '/PhysMem/ {print $6+$8}'"); + metrics.Free = float.Parse(free); + metrics.Used = metrics.Total - metrics.Free; + return metrics; + } +} + +public class ShellUtil +{ + /// + /// linux 系统命令 + /// + /// + /// + public static string Bash(string command) + { + var escapedArgs = command.Replace("\"", "\\\""); + var process = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + process.Start(); + string result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + process.Dispose(); + return result; + } + + /// + /// windows系统命令 + /// + /// + /// + /// + public static string Cmd(string fileName, string args) + { + var info = new ProcessStartInfo + { + FileName = fileName, + Arguments = args, + RedirectStandardOutput = true + }; + + var output = string.Empty; + using (var process = Process.Start(info)) + { + output = process.StandardOutput.ReadToEnd(); + } + return output; + } +} + +public class ShellHelper +{ + /// + /// Linux 系统命令 + /// + /// + /// + public static string Bash(string command) + { + var escapedArgs = command.Replace("\"", "\\\""); + var process = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + process.Start(); + string result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + process.Dispose(); + return result; + } + + /// + /// Windows 系统命令 + /// + /// + /// + /// + public static string Cmd(string fileName, string args) + { + var info = new ProcessStartInfo + { + FileName = fileName, + Arguments = args, + RedirectStandardOutput = true + }; + + var output = string.Empty; + using (var process = Process.Start(info)) + { + output = process.StandardOutput.ReadToEnd(); + } + return output; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/CryptogramUtil.cs b/Admin.NET/Admin.NET.Core/Util/CryptogramUtil.cs new file mode 100644 index 00000000..ebbedede --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/CryptogramUtil.cs @@ -0,0 +1,120 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public class CryptogramUtil +{ + public static readonly bool StrongPassword = App.GetConfig("Cryptogram:StrongPassword"); // 是否开启密码强度验证 + public static readonly string PasswordStrengthValidation = App.GetConfig("Cryptogram:PasswordStrengthValidation"); // 密码强度验证正则表达式 + public static readonly string PasswordStrengthValidationMsg = App.GetConfig("Cryptogram:PasswordStrengthValidationMsg"); // 密码强度验证提示 + public static readonly string CryptoType = App.GetConfig("Cryptogram:CryptoType"); // 加密类型 + public static readonly string PublicKey = App.GetConfig("Cryptogram:PublicKey"); // 公钥 + public static readonly string PrivateKey = App.GetConfig("Cryptogram:PrivateKey"); // 私钥 + + public static readonly string SM4_key = "0123456789abcdeffedcba9876543210"; + public static readonly string SM4_iv = "595298c7c6fd271f0402f804c33d3f66"; + + /// + /// 加密 + /// + /// + /// + public static string Encrypt(string plainText) + { + if (CryptoType == CryptogramEnum.MD5.ToString()) + { + return MD5Encryption.Encrypt(plainText); + } + else if (CryptoType == CryptogramEnum.SM2.ToString()) + { + return SM2Encrypt(plainText); + } + else if (CryptoType == CryptogramEnum.SM4.ToString()) + { + return SM4EncryptECB(plainText); + } + return plainText; + } + + /// + /// 解密 + /// + /// + /// + public static string Decrypt(string cipherText) + { + if (CryptoType == CryptogramEnum.SM2.ToString()) + { + return SM2Decrypt(cipherText); + } + else if (CryptoType == CryptogramEnum.SM4.ToString()) + { + return SM4DecryptECB(cipherText); + } + return cipherText; + } + + /// + /// SM2加密 + /// + /// + /// + public static string SM2Encrypt(string plainText) + { + return GMUtil.SM2Encrypt(PublicKey, plainText); + } + + /// + /// SM2解密 + /// + /// + /// + public static string SM2Decrypt(string cipherText) + { + return GMUtil.SM2Decrypt(PrivateKey, cipherText); + } + + /// + /// SM4加密(ECB) + /// + /// + /// + public static string SM4EncryptECB(string plainText) + { + return GMUtil.SM4EncryptECB(SM4_key, plainText); + } + + /// + /// SM4解密(ECB) + /// + /// + /// + public static string SM4DecryptECB(string cipherText) + { + return GMUtil.SM4DecryptECB(SM4_key, cipherText); + } + + /// + /// SM4加密(CBC) + /// + /// + /// + public static string SM4EncryptCBC(string plainText) + { + return GMUtil.SM4EncryptCBC(SM4_key, SM4_iv, plainText); + } + + /// + /// SM4解密(CBC) + /// + /// + /// + public static string SM4DecryptCBC(string cipherText) + { + return GMUtil.SM4DecryptCBC(SM4_key, SM4_iv, cipherText); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/DateTimeUtil.cs b/Admin.NET/Admin.NET.Core/Util/DateTimeUtil.cs new file mode 100644 index 00000000..070f2abb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/DateTimeUtil.cs @@ -0,0 +1,175 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public class DateTimeUtil +{ + /// + /// 获取开始时间 + /// + /// + /// + /// + public static DateTime GetBeginTime(DateTime? dateTime, int days = 0) + { + if (dateTime == DateTime.MinValue || dateTime == null) + return DateTime.Now.AddDays(days); + + return dateTime ?? DateTime.Now; + } + + /// + /// 时间戳转本地时间-时间戳精确到秒 + /// + public static DateTime ToLocalTimeDateBySeconds(long unix) + { + return DateTimeOffset.FromUnixTimeSeconds(unix).ToLocalTime().DateTime; + } + + /// + /// 时间转时间戳Unix-时间戳精确到秒 + /// + public static long ToUnixTimestampBySeconds(DateTime dt) + { + return new DateTimeOffset(dt).ToUnixTimeSeconds(); + } + + /// + /// 时间戳转本地时间-时间戳精确到毫秒 + /// + public static DateTime ToLocalTimeDateByMilliseconds(long unix) + { + return DateTimeOffset.FromUnixTimeMilliseconds(unix).ToLocalTime().DateTime; + } + + /// + /// 时间转时间戳Unix-时间戳精确到毫秒 + /// + public static long ToUnixTimestampByMilliseconds(DateTime dt) + { + return new DateTimeOffset(dt).ToUnixTimeMilliseconds(); + } + + /// + /// 毫秒转天时分秒 + /// + /// + /// + public static string FormatTime(long ms) + { + int ss = 1000; + int mi = ss * 60; + int hh = mi * 60; + int dd = hh * 24; + + long day = ms / dd; + long hour = (ms - day * dd) / hh; + long minute = (ms - day * dd - hour * hh) / mi; + long second = (ms - day * dd - hour * hh - minute * mi) / ss; + long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss; + + string sDay = day < 10 ? "0" + day : "" + day; //天 + string sHour = hour < 10 ? "0" + hour : "" + hour;//小时 + string sMinute = minute < 10 ? "0" + minute : "" + minute;//分钟 + string sSecond = second < 10 ? "0" + second : "" + second;//秒 + string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒 + sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond; + + return string.Format("{0} 天 {1} 小时 {2} 分 {3} 秒", sDay, sHour, sMinute, sSecond); + } + + /// + /// 获取unix时间戳 + /// + /// + /// + public static long GetUnixTimeStamp(DateTime dt) + { + return ((DateTimeOffset)dt).ToUnixTimeMilliseconds(); + } + + /// + /// 获取日期天的最小时间 + /// + /// + /// + public static DateTime GetDayMinDate(DateTime dt) + { + return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0); + } + + /// + /// 获取日期天的最大时间 + /// + /// + /// + + public static DateTime GetDayMaxDate(DateTime dt) + { + return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59); + } + + /// + /// 获取日期天的最大时间 + /// + /// + /// + public static string FormatDateTime(DateTime? dt) + { + if (dt == null) return string.Empty; + + if (dt.Value.Year == DateTime.Now.Year) + return dt.Value.ToString("MM-dd HH:mm"); + else + return dt.Value.ToString("yyyy-MM-dd HH:mm"); + } + + /// + /// 获取今天日期范围00:00:00 - 23:59:59 + /// + /// + public static List GetTodayTimeList(DateTime time) + { + return new List + { + Convert.ToDateTime(time.ToString("D").ToString()), + Convert.ToDateTime(time.AddDays(1).ToString("D").ToString()).AddSeconds(-1) + }; + } + + /// + /// 获取星期几 + /// + /// + /// + public static string GetWeekByDate(DateTime dt) + { + var day = new[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" }; + return day[Convert.ToInt32(dt.DayOfWeek.ToString("d"))]; + } + + /// + /// 获取这个月的第几周 + /// + /// + /// + public static int GetWeekNumInMonth(DateTime daytime) + { + int dayInMonth = daytime.Day; + // 本月第一天 + DateTime firstDay = daytime.AddDays(1 - daytime.Day); + // 本月第一天是周几 + int weekday = (int)firstDay.DayOfWeek == 0 ? 7 : (int)firstDay.DayOfWeek; + // 本月第一周有几天 + int firstWeekEndDay = 7 - (weekday - 1); + // 当前日期和第一周之差 + int diffday = dayInMonth - firstWeekEndDay; + diffday = diffday > 0 ? diffday : 1; + // 当前是第几周,若整除7就减一天 + return ((diffday % 7) == 0 ? (diffday / 7 - 1) : (diffday / 7)) + 1 + (dayInMonth > firstWeekEndDay ? 1 : 0); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/GM/GM.cs b/Admin.NET/Admin.NET.Core/Util/GM/GM.cs new file mode 100644 index 00000000..69ea7341 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/GM/GM.cs @@ -0,0 +1,471 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.GM; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509; + +namespace Admin.NET.Core; + +/** + * + * 用BC的注意点: + * 这个版本的BC对SM3withSM2的结果为asn1格式的r和s,如果需要直接拼接的r||s需要自己转换。下面rsAsn1ToPlainByteArray、rsPlainByteArrayToAsn1就在干这事。 + * 这个版本的BC对SM2的结果为C1||C2||C3,据说为旧标准,新标准为C1||C3||C2,用新标准的需要自己转换。下面(被注释掉的)changeC1C2C3ToC1C3C2、changeC1C3C2ToC1C2C3就在干这事。java版的高版本有加上C1C3C2,csharp版没准以后也会加,但目前还没有,java版的目前可以初始化时“ SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);”。 + * + */ + +public class GM +{ + private static X9ECParameters x9ECParameters = GMNamedCurves.GetByName("sm2p256v1"); + private static ECDomainParameters ecDomainParameters = new(x9ECParameters.Curve, x9ECParameters.G, x9ECParameters.N); + + /** + * + * @param msg + * @param userId + * @param privateKey + * @return r||s,直接拼接byte数组的rs + */ + + public static byte[] SignSm3WithSm2(byte[] msg, byte[] userId, AsymmetricKeyParameter privateKey) + { + return RsAsn1ToPlainByteArray(SignSm3WithSm2Asn1Rs(msg, userId, privateKey)); + } + + /** + * @param msg + * @param userId + * @param privateKey + * @return rs in asn1 format + */ + + public static byte[] SignSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, AsymmetricKeyParameter privateKey) + { + ISigner signer = SignerUtilities.GetSigner("SM3withSM2"); + signer.Init(true, new ParametersWithID(privateKey, userId)); + signer.BlockUpdate(msg, 0, msg.Length); + byte[] sig = signer.GenerateSignature(); + return sig; + } + + /** + * + * @param msg + * @param userId + * @param rs r||s,直接拼接byte数组的rs + * @param publicKey + * @return + */ + + public static bool VerifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, AsymmetricKeyParameter publicKey) + { + if (rs == null || msg == null || userId == null) return false; + if (rs.Length != RS_LEN * 2) return false; + return VerifySm3WithSm2Asn1Rs(msg, userId, RsPlainByteArrayToAsn1(rs), publicKey); + } + + /** + * + * @param msg + * @param userId + * @param rs in asn1 format + * @param publicKey + * @return + */ + + public static bool VerifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] sign, AsymmetricKeyParameter publicKey) + { + ISigner signer = SignerUtilities.GetSigner("SM3withSM2"); + signer.Init(false, new ParametersWithID(publicKey, userId)); + signer.BlockUpdate(msg, 0, msg.Length); + return signer.VerifySignature(sign); + } + + /** + * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2 + * @param c1c2c3 + * @return + */ + + private static byte[] ChangeC1C2C3ToC1C3C2(byte[] c1c2c3) + { + int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + const int c3Len = 32; //new SM3Digest().getDigestSize(); + byte[] result = new byte[c1c2c3.Length]; + Buffer.BlockCopy(c1c2c3, 0, result, 0, c1Len); //c1 + Buffer.BlockCopy(c1c2c3, c1c2c3.Length - c3Len, result, c1Len, c3Len); //c3 + Buffer.BlockCopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.Length - c1Len - c3Len); //c2 + return result; + } + + /** + * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密 + * @param c1c3c2 + * @return + */ + + private static byte[] ChangeC1C3C2ToC1C2C3(byte[] c1c3c2) + { + int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + const int c3Len = 32; //new SM3Digest().GetDigestSize(); + byte[] result = new byte[c1c3c2.Length]; + Buffer.BlockCopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65 + Buffer.BlockCopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.Length - c1Len - c3Len); //c2 + Buffer.BlockCopy(c1c3c2, c1Len, result, c1c3c2.Length - c3Len, c3Len); //c3 + return result; + } + + /** + * c1||c3||c2 + * @param data + * @param key + * @return + */ + + public static byte[] Sm2Decrypt(byte[] data, AsymmetricKeyParameter key) + { + return Sm2DecryptOld(ChangeC1C3C2ToC1C2C3(data), key); + } + + /** + * c1||c3||c2 + * @param data + * @param key + * @return + */ + + public static byte[] Sm2Encrypt(byte[] data, AsymmetricKeyParameter key) + { + return ChangeC1C2C3ToC1C3C2(Sm2EncryptOld(data, key)); + } + + /** + * c1||c2||c3 + * @param data + * @param key + * @return + */ + + public static byte[] Sm2EncryptOld(byte[] data, AsymmetricKeyParameter pubkey) + { + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.Init(true, new ParametersWithRandom(pubkey, new SecureRandom())); + return sm2Engine.ProcessBlock(data, 0, data.Length); + } + + /** + * c1||c2||c3 + * @param data + * @param key + * @return + */ + + public static byte[] Sm2DecryptOld(byte[] data, AsymmetricKeyParameter key) + { + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.Init(false, key); + return sm2Engine.ProcessBlock(data, 0, data.Length); + } + + /** + * @param bytes + * @return + */ + + public static byte[] Sm3(byte[] bytes) + { + SM3Digest digest = new(); + digest.BlockUpdate(bytes, 0, bytes.Length); + byte[] result = DigestUtilities.DoFinal(digest); + return result; + } + + private const int RS_LEN = 32; + + private static byte[] BigIntToFixexLengthBytes(BigInteger rOrS) + { + // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123, + // r and s are the result of mod n, so they should be less than n and have length<=32 + byte[] rs = rOrS.ToByteArray(); + if (rs.Length == RS_LEN) return rs; + else if (rs.Length == RS_LEN + 1 && rs[0] == 0) return Arrays.CopyOfRange(rs, 1, RS_LEN + 1); + else if (rs.Length < RS_LEN) + { + byte[] result = new byte[RS_LEN]; + Arrays.Fill(result, (byte)0); + Buffer.BlockCopy(rs, 0, result, RS_LEN - rs.Length, rs.Length); + return result; + } + else + { + throw new ArgumentException("err rs: " + Hex.ToHexString(rs)); + } + } + + /** + * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s + * @param rsDer rs in asn1 format + * @return sign result in plain byte array + */ + + private static byte[] RsAsn1ToPlainByteArray(byte[] rsDer) + { + Asn1Sequence seq = Asn1Sequence.GetInstance(rsDer); + byte[] r = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[0]).Value); + byte[] s = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[1]).Value); + byte[] result = new byte[RS_LEN * 2]; + Buffer.BlockCopy(r, 0, result, 0, r.Length); + Buffer.BlockCopy(s, 0, result, RS_LEN, s.Length); + return result; + } + + /** + * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式 + * @param sign in plain byte array + * @return rs result in asn1 format + */ + + private static byte[] RsPlainByteArrayToAsn1(byte[] sign) + { + if (sign.Length != RS_LEN * 2) throw new ArgumentException("err rs. "); + BigInteger r = new BigInteger(1, Arrays.CopyOfRange(sign, 0, RS_LEN)); + BigInteger s = new BigInteger(1, Arrays.CopyOfRange(sign, RS_LEN, RS_LEN * 2)); + Asn1EncodableVector v = new Asn1EncodableVector + { + new DerInteger(r), + new DerInteger(s) + }; + + return new DerSequence(v).GetEncoded("DER"); + } + + // 生成公私匙对 + public static AsymmetricCipherKeyPair GenerateKeyPair() + { + ECKeyPairGenerator kpGen = new(); + kpGen.Init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom())); + return kpGen.GenerateKeyPair(); + } + + public static ECPrivateKeyParameters GetPrivatekeyFromD(BigInteger d) + { + return new ECPrivateKeyParameters(d, ecDomainParameters); + } + + public static ECPublicKeyParameters GetPublickeyFromXY(BigInteger x, BigInteger y) + { + return new ECPublicKeyParameters(x9ECParameters.Curve.CreatePoint(x, y), ecDomainParameters); + } + + public static AsymmetricKeyParameter GetPublickeyFromX509File(FileInfo file) + { + FileStream fileStream = null; + try + { + //file.DirectoryName + "\\" + file.Name + fileStream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read); + X509Certificate certificate = new X509CertificateParser().ReadCertificate(fileStream); + return certificate.GetPublicKey(); + } + catch (Exception) + { + //log.Error(file.Name + "读取失败,异常:" + e); + } + finally + { + if (fileStream != null) + fileStream.Close(); + } + return null; + } + + public class Sm2Cert + { + public AsymmetricKeyParameter privateKey; + public AsymmetricKeyParameter publicKey; + public string certId; + } + + private static byte[] ToByteArray(int i) + { + byte[] byteArray = new byte[4]; + byteArray[0] = (byte)(i >> 24); + byteArray[1] = (byte)((i & 0xFFFFFF) >> 16); + byteArray[2] = (byte)((i & 0xFFFF) >> 8); + byteArray[3] = (byte)(i & 0xFF); + return byteArray; + } + + /** + * 字节数组拼接 + * + * @param params + * @return + */ + + private static byte[] Join(params byte[][] byteArrays) + { + List byteSource = new(); + for (int i = 0; i < byteArrays.Length; i++) + { + byteSource.AddRange(byteArrays[i]); + } + byte[] data = byteSource.ToArray(); + return data; + } + + /** + * 密钥派生函数 + * + * @param Z + * @param klen + * 生成klen字节数长度的密钥 + * @return + */ + + private static byte[] KDF(byte[] Z, int klen) + { + int ct = 1; + int end = (int)Math.Ceiling(klen * 1.0 / 32); + List byteSource = new(); + + for (int i = 1; i < end; i++) + { + byteSource.AddRange(Sm3(Join(Z, ToByteArray(ct)))); + ct++; + } + byte[] last = Sm3(Join(Z, ToByteArray(ct))); + if (klen % 32 == 0) + { + byteSource.AddRange(last); + } + else + byteSource.AddRange(Arrays.CopyOfRange(last, 0, klen % 32)); + return byteSource.ToArray(); + } + + public static byte[] Sm4DecryptCBC(byte[] keyBytes, byte[] cipher, byte[] iv, string algo) + { + if (keyBytes.Length != 16) throw new ArgumentException("err key length"); + if (cipher.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length"); + + KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); + IBufferedCipher c = CipherUtilities.GetCipher(algo); + if (iv == null) iv = ZeroIv(algo); + c.Init(false, new ParametersWithIV(key, iv)); + return c.DoFinal(cipher); + } + + public static byte[] Sm4EncryptCBC(byte[] keyBytes, byte[] plain, byte[] iv, string algo) + { + if (keyBytes.Length != 16) throw new ArgumentException("err key length"); + if (plain.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length"); + + KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); + IBufferedCipher c = CipherUtilities.GetCipher(algo); + if (iv == null) iv = ZeroIv(algo); + c.Init(true, new ParametersWithIV(key, iv)); + return c.DoFinal(plain); + } + + public static byte[] Sm4EncryptECB(byte[] keyBytes, byte[] plain, string algo) + { + if (keyBytes.Length != 16) throw new ArgumentException("err key length"); + //NoPadding 的情况下需要校验数据长度是16的倍数. + if (plain.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length"); + + KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); + IBufferedCipher c = CipherUtilities.GetCipher(algo); + c.Init(true, key); + return c.DoFinal(plain); + } + + public static byte[] Sm4DecryptECB(byte[] keyBytes, byte[] cipher, string algo) + { + if (keyBytes.Length != 16) throw new ArgumentException("err key length"); + if (cipher.Length % 16 != 0 && algo.Contains("NoPadding")) throw new ArgumentException("err data length"); + + KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); + IBufferedCipher c = CipherUtilities.GetCipher(algo); + c.Init(false, key); + return c.DoFinal(cipher); + } + + public const string SM4_ECB_NOPADDING = "SM4/ECB/NoPadding"; + public const string SM4_CBC_NOPADDING = "SM4/CBC/NoPadding"; + public const string SM4_ECB_PKCS7PADDING = "SM4/ECB/PKCS7Padding"; + public const string SM4_CBC_PKCS7PADDING = "SM4/CBC/PKCS7Padding"; + + /** + * cfca官网CSP沙箱导出的sm2文件 + * @param pem 二进制原文 + * @param pwd 密码 + * @return + */ + + public static Sm2Cert ReadSm2File(byte[] pem, string pwd) + { + Sm2Cert sm2Cert = new(); + + Asn1Sequence asn1Sequence = (Asn1Sequence)Asn1Object.FromByteArray(pem); + // ASN1Integer asn1Integer = (ASN1Integer) asn1Sequence.getObjectAt(0); //version=1 + Asn1Sequence priSeq = (Asn1Sequence)asn1Sequence[1];//private key + Asn1Sequence pubSeq = (Asn1Sequence)asn1Sequence[2];//public key and x509 cert + + // ASN1ObjectIdentifier sm2DataOid = (ASN1ObjectIdentifier) priSeq.getObjectAt(0); + // ASN1ObjectIdentifier sm4AlgOid = (ASN1ObjectIdentifier) priSeq.getObjectAt(1); + Asn1OctetString priKeyAsn1 = (Asn1OctetString)priSeq[2]; + byte[] key = KDF(System.Text.Encoding.UTF8.GetBytes(pwd), 32); + byte[] priKeyD = Sm4DecryptCBC(Arrays.CopyOfRange(key, 16, 32), + priKeyAsn1.GetOctets(), + Arrays.CopyOfRange(key, 0, 16), SM4_CBC_PKCS7PADDING); + sm2Cert.privateKey = GetPrivatekeyFromD(new BigInteger(1, priKeyD)); + // log.Info(Hex.toHexString(priKeyD)); + + // ASN1ObjectIdentifier sm2DataOidPub = (ASN1ObjectIdentifier) pubSeq.getObjectAt(0); + Asn1OctetString pubKeyX509 = (Asn1OctetString)pubSeq[1]; + X509Certificate x509 = new X509CertificateParser().ReadCertificate(pubKeyX509.GetOctets()); + sm2Cert.publicKey = x509.GetPublicKey(); + sm2Cert.certId = x509.SerialNumber.ToString(10); //这里转10进制,有啥其他进制要求的自己改改 + return sm2Cert; + } + + /** + * + * @param cert + * @return + */ + + public static Sm2Cert ReadSm2X509Cert(byte[] cert) + { + Sm2Cert sm2Cert = new(); + + X509Certificate x509 = new X509CertificateParser().ReadCertificate(cert); + sm2Cert.publicKey = x509.GetPublicKey(); + sm2Cert.certId = x509.SerialNumber.ToString(10); //这里转10进制,有啥其他进制要求的自己改改 + return sm2Cert; + } + + public static byte[] ZeroIv(string algo) + { + IBufferedCipher cipher = CipherUtilities.GetCipher(algo); + int blockSize = cipher.GetBlockSize(); + byte[] iv = new byte[blockSize]; + Arrays.Fill(iv, (byte)0); + return iv; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/GM/GMUtil.cs b/Admin.NET/Admin.NET.Core/Util/GM/GMUtil.cs new file mode 100644 index 00000000..d92853ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/GM/GMUtil.cs @@ -0,0 +1,151 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Admin.NET.Core; + +/// +/// GM工具类 +/// +public class GMUtil +{ + /// + /// SM2加密 + /// + /// + /// + /// + public static string SM2Encrypt(string publicKeyHex, string data_string) + { + // 如果是130位公钥,.NET使用的话,把开头的04截取掉 + if (publicKeyHex.Length == 130) + { + publicKeyHex = publicKeyHex.Substring(2, 128); + } + // 公钥X,前64位 + string x = publicKeyHex.Substring(0, 64); + // 公钥Y,后64位 + string y = publicKeyHex.Substring(64); + // 获取公钥对象 + AsymmetricKeyParameter publicKey1 = GM.GetPublickeyFromXY(new BigInteger(x, 16), new BigInteger(y, 16)); + // Sm2Encrypt: C1C3C2 + // Sm2EncryptOld: C1C2C3 + byte[] digestByte = GM.Sm2Encrypt(Encoding.UTF8.GetBytes(data_string), publicKey1); + string strSM2 = Hex.ToHexString(digestByte); + return strSM2; + } + + /// + /// SM2解密 + /// + /// + /// + /// + public static string SM2Decrypt(string privateKey_string, string encryptedData_string) + { + if (!encryptedData_string.StartsWith("04")) + encryptedData_string = "04" + encryptedData_string; + BigInteger d = new(privateKey_string, 16); + // 先拿到私钥对象,用ECPrivateKeyParameters 或 AsymmetricKeyParameter 都可以 + // ECPrivateKeyParameters bcecPrivateKey = GmUtil.GetPrivatekeyFromD(d); + AsymmetricKeyParameter bcecPrivateKey = GM.GetPrivatekeyFromD(d); + byte[] byToDecrypt = Hex.Decode(encryptedData_string); + byte[] byDecrypted = GM.Sm2Decrypt(byToDecrypt, bcecPrivateKey); + string strDecrypted = Encoding.UTF8.GetString(byDecrypted); + return strDecrypted; + } + + /// + /// SM4加密(ECB) + /// + /// + /// + /// + public static string SM4EncryptECB(string key_string, string plainText) + { + byte[] key = Hex.Decode(key_string); + byte[] bs = GM.Sm4EncryptECB(key, Encoding.UTF8.GetBytes(plainText), GM.SM4_ECB_PKCS7PADDING);//NoPadding 的情况下需要校验数据长度是16的倍数. 使用 HandleSm4Padding 处理 + return Hex.ToHexString(bs); + } + + /// + /// SM4解密(ECB) + /// + /// + /// + /// + public static string SM4DecryptECB(string key_string, string cipherText) + { + byte[] key = Hex.Decode(key_string); + byte[] bs = GM.Sm4DecryptECB(key, Hex.Decode(cipherText), GM.SM4_ECB_PKCS7PADDING); + return Encoding.UTF8.GetString(bs); + } + + /// + /// SM4加密(CBC) + /// + /// + /// + /// + /// + public static string SM4EncryptCBC(string key_string, string iv_string, string plainText) + { + byte[] key = Hex.Decode(key_string); + byte[] iv = Hex.Decode(iv_string); + byte[] bs = GM.Sm4EncryptCBC(key, Encoding.UTF8.GetBytes(plainText), iv, GM.SM4_CBC_PKCS7PADDING); + return Hex.ToHexString(bs); + } + + /// + /// SM4解密(CBC) + /// + /// + /// + /// + /// + public static string SM4DecryptCBC(string key_string, string iv_string, string cipherText) + { + byte[] key = Hex.Decode(key_string); + byte[] iv = Hex.Decode(iv_string); + byte[] bs = GM.Sm4DecryptCBC(key, Hex.Decode(cipherText), iv, GM.SM4_CBC_PKCS7PADDING); + return Encoding.UTF8.GetString(bs); + } + + /// + /// 补足 16 进制字符串的 0 字符,返回不带 0x 的16进制字符串 + /// + /// + /// 1表示加密,0表示解密 + /// + private static byte[] HandleSm4Padding(byte[] input, int mode) + { + if (input == null) + { + return null; + } + byte[] ret = (byte[])null; + if (mode == 1) + { + int p = 16 - input.Length % 16; + ret = new byte[input.Length + p]; + Array.Copy(input, 0, ret, 0, input.Length); + for (int i = 0; i < p; i++) + { + ret[input.Length + i] = (byte)p; + } + } + else + { + int p = input[input.Length - 1]; + ret = new byte[input.Length - p]; + Array.Copy(input, 0, ret, 0, input.Length - p); + } + return ret; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/NewtonsoftJsonSerializerProvider.cs b/Admin.NET/Admin.NET.Core/Util/NewtonsoftJsonSerializerProvider.cs new file mode 100644 index 00000000..568ffbd9 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/NewtonsoftJsonSerializerProvider.cs @@ -0,0 +1,59 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Newtonsoft.Json; + +namespace Admin.NET.Core; + +/// +/// 自定义序列化提供器 Newtonsoft.Json 实现 +/// +public class NewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ISingleton +{ + /// + /// 序列化对象 + /// + /// + /// + /// + public string Serialize(object value, object jsonSerializerOptions = null) + { + return JsonConvert.SerializeObject(value, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings); + } + + /// + /// 反序列化字符串 + /// + /// + /// + /// + /// + public T Deserialize(string json, object jsonSerializerOptions = null) + { + return JsonConvert.DeserializeObject(json, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings); + } + + /// + /// 反序列化字符串 + /// + /// + /// + /// + /// + public object Deserialize(string json, Type returnType, object jsonSerializerOptions = null) + { + return JsonConvert.DeserializeObject(json, returnType, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings); + } + + /// + /// 返回读取全局配置的 JSON 选项 + /// + /// + public object GetSerializerOptions() + { + return App.GetOptions()?.SerializerSettings; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/ReflectionUtil.cs b/Admin.NET/Admin.NET.Core/Util/ReflectionUtil.cs new file mode 100644 index 00000000..0797a1d5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/ReflectionUtil.cs @@ -0,0 +1,28 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 反射工具类 +/// +public static class ReflectionUtil +{ + /// + /// 获取字段特性 + /// + /// + /// + /// + public static T GetDescriptionValue(this FieldInfo field) where T : Attribute + { + // 获取字段的指定特性,不包含继承中的特性 + object[] customAttributes = field.GetCustomAttributes(typeof(T), false); + + // 如果没有数据返回null + return customAttributes.Length > 0 ? (T)customAttributes[0] : null; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/RegularValidate.cs b/Admin.NET/Admin.NET.Core/Util/RegularValidate.cs new file mode 100644 index 00000000..b7c8bfe3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/RegularValidate.cs @@ -0,0 +1,36 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 正则校验 +/// +public static class RegularValidate +{ + /// + /// 验证密码规则 + /// + /// + /// + public static bool ValidatePassword(string password) + { + var regex = new Regex(@" +(?=.*[0-9]) #必须包含数字 +(?=.*[a-z]) #必须包含小写 +(?=.*[A-Z]) #必须包含大写 +(?=([\x21-\x7e]+)[^a-zA-Z0-9]) #必须包含特殊符号 +.{8,30} #至少8个字符,最多30个字符 +", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); + + //如果要求必须包含小写、大写字母,则上面的(?=.*[a-zA-Z]) 要改为: + /* + * (?=.*[a-z]) + * (?=.*[A-Z]) + */ + return regex.IsMatch(password); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/SSHHelper.cs b/Admin.NET/Admin.NET.Core/Util/SSHHelper.cs new file mode 100644 index 00000000..82050436 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/SSHHelper.cs @@ -0,0 +1,212 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Renci.SshNet; + +namespace Admin.NET.Core +{ + /// + /// SSH/Sftp 工具类 + /// + public class SSHHelper : IDisposable + { + private readonly SftpClient _sftp; + + public SSHHelper(string host, int port, string user, string password) + { + _sftp = new SftpClient(host, port, user, password); + } + + /// + /// 连接 + /// + private void Connect() + { + if (!_sftp.IsConnected) + _sftp.Connect(); + } + + /// + /// 是否存在同名文件 + /// + /// + /// + public bool Exists(string ftpFileName) + { + Connect(); + + return _sftp.Exists(ftpFileName); + } + + /// + /// 删除文件 + /// + /// + public void DeleteFile(string ftpFileName) + { + Connect(); + + _sftp.DeleteFile(ftpFileName); + } + + /// + /// 下载到指定目录 + /// + /// + /// + public void DownloadFile(string ftpFileName, string localFileName) + { + Connect(); + + using (Stream fileStream = File.OpenWrite(localFileName)) + { + _sftp.DownloadFile(ftpFileName, fileStream); + } + } + + /// + /// 读取字节 + /// + /// + /// + public byte[] ReadAllBytes(string ftpFileName) + { + Connect(); + + return _sftp.ReadAllBytes(ftpFileName); + } + + /// + /// 读取流 + /// + /// + /// + public Stream OpenRead(string path) + { + return _sftp.Open(path, FileMode.Open, FileAccess.Read); + } + + /// + /// 继续下载 + /// + /// + /// + public void DownloadFileWithResume(string ftpFileName, string localFileName) + { + DownloadFile(ftpFileName, localFileName); + } + + /// + /// 重命名 + /// + /// + /// + public void RenameFile(string oldPath, string newPath) + { + _sftp.RenameFile(oldPath, newPath); + } + + /// + /// 指定目录下文件 + /// + /// + /// + /// + public List GetFileList(string folder, IEnumerable filters) + { + Connect(); + + var files = new List(); + var sftpFiles = _sftp.ListDirectory(folder); + foreach (var file in sftpFiles) + { + if (file.IsRegularFile && filters.Any(f => file.Name.EndsWith(f))) + files.Add(file.Name); + } + return files; + } + + /// + /// 上传指定目录文件 + /// + /// + /// + public void UploadFile(string localFileName, string ftpFileName) + { + Connect(); + + var dir = Path.GetDirectoryName(ftpFileName); + CreateDir(_sftp, dir); + using (var fileStream = new FileStream(localFileName, FileMode.Open)) + { + _sftp.UploadFile(fileStream, ftpFileName); + } + } + + /// + /// 上传字节 + /// + /// + /// + public void UploadFile(byte[] bs, string ftpFileName) + { + Connect(); + + var dir = Path.GetDirectoryName(ftpFileName); + CreateDir(_sftp, dir); + _sftp.WriteAllBytes(ftpFileName, bs); + } + + /// + /// 上传流 + /// + /// + /// + public void UploadFile(Stream fileStream, string ftpFileName) + { + Connect(); + + var dir = Path.GetDirectoryName(ftpFileName); + CreateDir(_sftp, dir); + _sftp.UploadFile(fileStream, ftpFileName); + fileStream.Dispose(); + } + + /// + /// 创建目录 + /// + /// + /// + /// + private void CreateDir(SftpClient sftp, string dir) + { + ArgumentNullException.ThrowIfNull(dir); + + if (sftp.Exists(dir)) return; + + var index = dir.LastIndexOfAny(new char[] { '/', '\\' }); + if (index > 0) + { + var p = dir[..index]; + if (!sftp.Exists(p)) + CreateDir(sftp, p); + sftp.CreateDirectory(dir); + } + } + + /// + /// 释放对象 + /// + public void Dispose() + { + if (_sftp == null) return; + + if (_sftp.IsConnected) + _sftp.Disconnect(); + _sftp.Dispose(); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/TripleDES.cs b/Admin.NET/Admin.NET.Core/Util/TripleDES.cs new file mode 100644 index 00000000..5410eae3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/TripleDES.cs @@ -0,0 +1,53 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Security.Cryptography; + +namespace Admin.NET.Core; + +/// +/// 3DES文件加解密 +/// +public static class TripleDES +{ + /// + /// 加密文件 + /// + /// 待加密文件路径 + /// 加密后的文件路径 + /// 密码 (24位长度) + [Obsolete] + public static void EncryptFile(string inputFile, string outputFile, string password) + { + using var tdes = new TripleDESCryptoServiceProvider(); + tdes.Mode = CipherMode.ECB; + tdes.Padding = PaddingMode.PKCS7; + tdes.Key = Encoding.UTF8.GetBytes(password); + using var inputFileStream = new FileStream(inputFile, FileMode.Open); + using var encryptedFileStream = new FileStream(outputFile, FileMode.Create); + using var cryptoStream = new CryptoStream(encryptedFileStream, tdes.CreateEncryptor(), CryptoStreamMode.Write); + inputFileStream.CopyTo(cryptoStream); + } + + /// + /// 加密文件 + /// + /// 加密的文件路径 + /// 解密后的文件路径 + /// 密码 (24位长度) + [Obsolete] + public static void DecryptFile(string inputFile, string outputFile, string password) + { + using var tdes = new TripleDESCryptoServiceProvider(); + tdes.Mode = CipherMode.ECB; + tdes.Padding = PaddingMode.PKCS7; + tdes.Key = Encoding.UTF8.GetBytes(password); + using var encryptedFileStream = new FileStream(inputFile, FileMode.Open); + using var decryptedFileStream = new FileStream(outputFile, FileMode.Create); + using var cryptoStream = new CryptoStream(encryptedFileStream, tdes.CreateDecryptor(), CryptoStreamMode.Read); + cryptoStream.CopyTo(decryptedFileStream); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/VerifyFileExtensionName.cs b/Admin.NET/Admin.NET.Core/Util/VerifyFileExtensionName.cs new file mode 100644 index 00000000..18d13aa1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/VerifyFileExtensionName.cs @@ -0,0 +1,164 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 验证文件类型 +/// +public static class VerifyFileExtensionName +{ + private static readonly IDictionary dics_ext = new Dictionary(); + private static readonly IDictionary> ext_dics = new Dictionary>(); + + static VerifyFileExtensionName() + { + dics_ext.Add("FFD8FFE0", ".jpg"); + dics_ext.Add("FFD8FFE1", ".jpg"); + dics_ext.Add("89504E47", ".png"); + dics_ext.Add("47494638", ".gif"); + dics_ext.Add("49492A00", ".tif"); + dics_ext.Add("424D", ".bmp"); + + // PS和CAD + dics_ext.Add("38425053", ".psd"); + dics_ext.Add("41433130", ".dwg"); // CAD + dics_ext.Add("252150532D41646F6265", ".ps"); + + // 办公文档类 + dics_ext.Add("D0CF11E0", ".ppt,.doc,.xls"); // ppt、doc、xls + dics_ext.Add("504B0304", ".pptx,.docx,.xlsx"); // pptx、docx、xlsx + + /* 注意由于文本文档录入内容过多,则读取文件头时较为多变-START */ + dics_ext.Add("0D0A0D0A", ".txt"); // txt + dics_ext.Add("0D0A2D2D", ".txt"); // txt + dics_ext.Add("0D0AB4B4", ".txt"); // txt + dics_ext.Add("B4B4BDA8", ".txt"); // 文件头部为汉字 + dics_ext.Add("73646673", ".txt"); // txt,文件头部为英文字母 + dics_ext.Add("32323232", ".txt"); // txt,文件头部内容为数字 + dics_ext.Add("0D0A09B4", ".txt"); // txt,文件头部内容为数字 + dics_ext.Add("3132330D", ".txt"); // txt,文件头部内容为数字 + /* 注意由于文本文档录入内容过多,则读取文件头时较为多变-END */ + + dics_ext.Add("7B5C727466", ".rtf"); // 日记本 + + dics_ext.Add("255044462D312E", ".pdf"); + + // 视频或音频类 + dics_ext.Add("3026B275", ".wma"); + dics_ext.Add("57415645", ".wav"); + dics_ext.Add("41564920", ".avi"); + dics_ext.Add("4D546864", ".mid"); + dics_ext.Add("2E524D46", ".rm"); + dics_ext.Add("000001BA", ".mpg"); + dics_ext.Add("000001B3", ".mpg"); + dics_ext.Add("6D6F6F76", ".mov"); + dics_ext.Add("3026B2758E66CF11", ".asf"); + + // 压缩包 + dics_ext.Add("52617221", ".rar"); + dics_ext.Add("504B03040A000000", ".zip"); + dics_ext.Add("1F8B08", ".gz"); + + // 程序文件 + dics_ext.Add("3C3F786D6C", ".xml"); + dics_ext.Add("68746D6C3E", ".html"); + //dics_ext.Add("7061636B", ".java"); + //dics_ext.Add("3C254020", ".jsp"); + //dics_ext.Add("4D5A9000", ".exe"); + + dics_ext.Add("44656C69766572792D646174653A", ".eml"); // 邮件 + dics_ext.Add("5374616E64617264204A", ".mdb"); // Access数据库文件 + + dics_ext.Add("46726F6D", ".mht"); + dics_ext.Add("4D494D45", ".mhtml"); + + foreach (var dics in dics_ext) + { + foreach (var ext in dics.Value.Split(",")) + { + if (!ext_dics.ContainsKey(ext)) + ext_dics.Add(ext, new HashSet { dics.Key.Length / 2 }); + else + ext_dics[ext].Add(dics.Key.Length / 2); + } + } + } + + /// + /// 文件格式和文件内容格式是否一致 + /// + /// + /// + /// + public static bool IsSameType(Stream stream, string suffix = ".jpg") + { + if (stream == null) + return false; + + suffix = suffix.ToLower(); + if (!ext_dics.ContainsKey(suffix)) + return false; + + try + { + foreach (var Len in ext_dics[suffix]) + { + byte[] b = new byte[Len]; + stream.Read(b, 0, b.Length); + // string fileType = System.Text.Encoding.UTF8.GetString(b); + string fileKey = GetFileHeader(b); + if (dics_ext.ContainsKey(fileKey)) + return true; + } + } + catch (IOException) + { + } + return false; + } + + /** + * 根据文件转换成的字节数组获取文件头信息 + * @param 文件路径 + * @return 文件头信息 + */ + + private static string GetFileHeader(byte[] b) + { + string value = BytesToHexString(b); + return value; + } + + /** + * 将要读取文件头信息的文件的byte数组转换成string类型表示 + * 下面这段代码就是用来对文件类型作验证的方法, + * 将字节数组的前四位转换成16进制字符串,并且转换的时候,要先和0xFF做一次与运算。 + * 这是因为,整个文件流的字节数组中,有很多是负数,进行了与运算后,可以将前面的符号位都去掉, + * 这样转换成的16进制字符串最多保留两位,如果是正数又小于10,那么转换后只有一位, + * 需要在前面补0,这样做的目的是方便比较,取完前四位这个循环就可以终止了 + * @param src要读取文件头信息的文件的byte数组 + * @return 文件头信息 + */ + + private static string BytesToHexString(byte[] src) + { + var builder = new StringBuilder(); + if (src == null || src.Length <= 0) + return null; + + string hv; + for (int i = 0; i < src.Length; i++) + { + // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写 + hv = Convert.ToString(src[i] & 0xFF, 16).ToUpper(); + if (hv.Length < 2) + builder.Append(0); + builder.Append(hv); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj b/Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj new file mode 100644 index 00000000..245911c5 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj @@ -0,0 +1,21 @@ + + + + net6.0;net8.0 + 1701;1702;1591 + + True + Admin.NET + Admin.NET 通用权限开发平台 + + + + + + + + + + + + diff --git a/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs new file mode 100644 index 00000000..87261c4b --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs @@ -0,0 +1,92 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core; +using Admin.NET.Core.Service; +using Furion; +using Furion.Authorization; +using Furion.DataEncryption; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace Admin.NET.Web.Core +{ + public class JwtHandler : AppAuthorizeHandler + { + private readonly IServiceProvider _serviceProvider; + + public JwtHandler(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// 自动刷新Token + /// + /// + /// + /// + public override async Task HandleAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) + { + // var serviceProvider = context.GetCurrentHttpContext().RequestServices; + using var serviceScope = _serviceProvider.CreateScope(); + + // 若当前账号存在黑名单中则授权失败 + var sysCacheService = serviceScope.ServiceProvider.GetRequiredService(); + if (sysCacheService.ExistKey($"{CacheConst.KeyBlacklist}{context.User.FindFirst(ClaimConst.UserId)?.Value}")) + { + context.Fail(); + context.GetCurrentHttpContext().SignoutToSwagger(); + return; + } + + var sysConfigService = serviceScope.ServiceProvider.GetRequiredService(); + var tokenExpire = await sysConfigService.GetTokenExpire(); + var refreshTokenExpire = await sysConfigService.GetRefreshTokenExpire(); + if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext(), tokenExpire, refreshTokenExpire)) + { + await AuthorizeHandleAsync(context); + } + else + { + context.Fail(); // 授权失败 + var currentHttpContext = context.GetCurrentHttpContext(); + if (currentHttpContext == null) + return; + // 跳过由于 SignatureAuthentication 引发的失败 + if (currentHttpContext.Items.ContainsKey(SignatureAuthenticationDefaults.AuthenticateFailMsgKey)) + return; + currentHttpContext.SignoutToSwagger(); + } + } + + public override async Task PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) + { + // 已自动验证 Jwt Token 有效性 + return await CheckAuthorizeAsync(httpContext); + } + + /// + /// 权限校验核心逻辑 + /// + /// + /// + private static async Task CheckAuthorizeAsync(DefaultHttpContext httpContext) + { + // 排除超管权限 + if (App.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString()) + return true; + + // 接口路由权限 + var path = httpContext.Request.Path.ToString(); + var apis = await App.GetRequiredService().GetUserApiList(); + return apis.Exists(u => path.Contains(u, StringComparison.CurrentCulture)); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs b/Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs new file mode 100644 index 00000000..d0fb0b53 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs @@ -0,0 +1,47 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core; +using AspNetCoreRateLimit; +using Furion; +using Microsoft.Extensions.DependencyInjection; + +namespace Admin.NET.Web.Core; + +public static class ProjectOptions +{ + /// + /// 注册项目配置选项 + /// + /// + /// + public static IServiceCollection AddProjectOptions(this IServiceCollection services) + { + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.AddConfigurableOptions(); + services.Configure(App.Configuration.GetSection("IpRateLimiting")); + services.Configure(App.Configuration.GetSection("IpRateLimitPolicies")); + services.Configure(App.Configuration.GetSection("ClientRateLimiting")); + services.Configure(App.Configuration.GetSection("ClientRateLimitPolicies")); + + return services; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Core/Startup.cs b/Admin.NET/Admin.NET.Web.Core/Startup.cs new file mode 100644 index 00000000..ac622c29 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Core/Startup.cs @@ -0,0 +1,270 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core; +using Admin.NET.Core.Service; +using AspNetCoreRateLimit; +using Furion; +using Furion.SpecificationDocument; +using Furion.VirtualFileServer; +using IGeekFan.AspNetCore.Knife4jUI; +using IPTools.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using OnceMi.AspNetCore.OSS; +using SixLabors.ImageSharp.Web.DependencyInjection; +using System; + +namespace Admin.NET.Web.Core; + +public class Startup : AppStartup +{ + public void ConfigureServices(IServiceCollection services) + { + // 配置选项 + services.AddProjectOptions(); + + // 缓存注册 + services.AddCache(); + // SqlSugar + services.AddSqlSugar(); + // JWT + services.AddJwt(enableGlobalAuthorize: true) + // 添加 Signature 身份验证 + .AddSignatureAuthentication(options => + { + options.Events = SysOpenAccessService.GetSignatureAuthenticationEventImpl(); + }); + // 允许跨域 + services.AddCorsAccessor(); + // 远程请求 + services.AddRemoteRequest(); + // 任务队列 + services.AddTaskQueue(); + // 任务调度 + services.AddSchedule(options => + { + options.AddPersistence(); // 添加作业持久化器 + options.AddMonitor(); // 添加作业执行监视器 + }); + // 脱敏检测 + services.AddSensitiveDetection(); + + // Json序列化设置 + static void SetNewtonsoftJsonSetting(JsonSerializerSettings setting) + { + setting.DateFormatHandling = DateFormatHandling.IsoDateFormat; + setting.DateTimeZoneHandling = DateTimeZoneHandling.Local; + setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化 + setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用 + // setting.ContractResolver = new CamelCasePropertyNamesContractResolver(); // 解决动态对象属性名大写 + // setting.NullValueHandling = NullValueHandling.Ignore; // 忽略空值 + // setting.Converters.AddLongTypeConverters(); // long转string(防止js精度溢出) 超过17位开启 + // setting.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; // 解决DateTimeOffset异常 + // setting.DateParseHandling = DateParseHandling.None; // 解决DateTimeOffset异常 + // setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常 + }; + + services.AddControllersWithViews() + .AddAppLocalization() + .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings)) + //.AddXmlSerializerFormatters() + //.AddXmlDataContractSerializerFormatters() + .AddInjectWithUnifyResult(); + + // 三方授权登录OAuth + services.AddOAuth(); + + // ElasticSearch + services.AddElasticSearch(); + + // 配置Nginx转发获取客户端真实IP + // 注1:如果负载均衡不是在本机通过 Loopback 地址转发请求的,一定要加上options.KnownNetworks.Clear()和options.KnownProxies.Clear() + // 注2:如果设置环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 True,则不需要下面的配置代码 + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.All; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); + + // 限流服务 + services.AddInMemoryRateLimiting(); + services.AddSingleton(); + + // 事件总线 + services.AddEventBus(options => + { + options.UseUtcTimestamp = false; + // 不启用事件日志 + options.LogEnabled = false; + // 事件执行器(失败重试) + options.AddExecutor(); + + #region Redis消息队列 + + //// 替换事件源存储器 + //options.ReplaceStorer(serviceProvider => + //{ + // var redisCache = serviceProvider.GetRequiredService(); + // // 创建默认内存通道事件源对象,可自定义队列路由key,如:adminnet + // return new RedisEventSourceStorer(redisCache, "adminnet", 3000); + //}); + + #endregion Redis消息队列 + + #region RabbitMQ消息队列 + + //// 创建默认内存通道事件源对象,可自定义队列路由key,如:adminnet + //var eventBusOpt = App.GetConfig("EventBus", true); + //var rbmqEventSourceStorer = new RabbitMQEventSourceStore(new ConnectionFactory + //{ + // UserName = eventBusOpt.RabbitMQ.UserName, + // Password = eventBusOpt.RabbitMQ.Password, + // HostName = eventBusOpt.RabbitMQ.HostName, + // Port = eventBusOpt.RabbitMQ.Port + //}, "adminnet", 3000); + + //// 替换默认事件总线存储器 + //options.ReplaceStorer(serviceProvider => + //{ + // return rbmqEventSourceStorer; + //}); + + #endregion RabbitMQ消息队列 + }); + + // 图像处理 + services.AddImageSharp(); + + // OSS对象存储 + var ossOpt = App.GetConfig("OSSProvider", true); + services.AddOSSService(Enum.GetName(ossOpt.Provider), "OSSProvider"); + + // 模板引擎 + services.AddViewEngine(); + + // 即时通讯 + services.AddSignalR(SetNewtonsoftJsonSetting); + //services.AddSingleton(); + + // 系统日志 + services.AddLoggingSetup(); + + // 验证码 + services.AddCaptcha(); + + // 控制台logo + services.AddConsoleLogo(); + + // 将IP地址数据库文件完全加载到内存,提升查询速度(以空间换时间,内存将会增加60-70M) + IpToolSettings.LoadInternationalDbToMemory = true; + // 设置默认查询器China和International + //IpToolSettings.DefalutSearcherType = IpSearcherType.China; + IpToolSettings.DefalutSearcherType = IpSearcherType.International; + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseForwardedHeaders(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); + } + + app.Use(async (context, next) => + { + context.Response.Headers.Append("Admin.NET", "Admin.NET"); + await next(); + }); + + // 图像处理 + app.UseImageSharp(); + + // 特定文件类型(文件后缀)处理 + var contentTypeProvider = FS.GetFileExtensionContentTypeProvider(); + // contentTypeProvider.Mappings[".文件后缀"] = "MIME 类型"; + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = contentTypeProvider + }); + + //// 启用HTTPS + //app.UseHttpsRedirection(); + + // 启用OAuth + app.UseOAuth(); + + // 添加状态码拦截中间件 + app.UseUnifyResultStatusCodes(); + + // 启用多语言,必须在 UseRouting 之前 + app.UseAppLocalization(); + + // 路由注册 + app.UseRouting(); + + // 启用跨域,必须在 UseRouting 和 UseAuthentication 之间注册 + app.UseCorsAccessor(); + + // 启用鉴权授权 + app.UseAuthentication(); + app.UseAuthorization(); + + // 限流组件(在跨域之后) + app.UseIpRateLimiting(); + app.UseClientRateLimiting(); + + // 任务调度看板 + app.UseScheduleUI(options => + { + options.RequestPath = "/schedule"; // 必须以 / 开头且不以 / 结尾 + options.DisableOnProduction = true; // 生产环境关闭 + options.DisplayEmptyTriggerJobs = true; // 是否显示空作业触发器的作业 + options.DisplayHead = false; // 是否显示页头 + options.DefaultExpandAllJobs = false; // 是否默认展开所有作业 + }); + + // 配置Swagger-Knife4UI(路由前缀一致代表独立,不同则代表共存) + app.UseKnife4UI(options => + { + options.RoutePrefix = "kapi"; + foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups()) + { + options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title); + } + }); + + app.UseInject(string.Empty, options => + { + foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups()) + { + groupInfo.Description += "
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!"; + } + }); + + app.UseEndpoints(endpoints => + { + // 注册集线器 + endpoints.MapHubs(); + + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + }); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj b/Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj new file mode 100644 index 00000000..cc66d904 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj @@ -0,0 +1,60 @@ + + + + net6.0;net8.0 + enable + zh-Hans + true + false + ad9369d1-f29b-4f8f-a7df-8b4d7aa0726b + Linux + true + Admin.NET + Admin.NET 通用权限开发平台 + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + PreserveNewest + + + PreserveNewest + + + + + + Never + + + + + + PreserveNewest + + + + + + + + diff --git a/Admin.NET/Admin.NET.Web.Entry/Controllers/HomeController.cs b/Admin.NET/Admin.NET.Web.Entry/Controllers/HomeController.cs new file mode 100644 index 00000000..97ab9521 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Controllers/HomeController.cs @@ -0,0 +1,29 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Admin.NET.Web.Entry.Controllers +{ + [AllowAnonymous] + public class HomeController : Controller + { + //private readonly ISystemService _systemService; + + //public HomeController(ISystemService systemService) + //{ + // _systemService = systemService; + //} + + public IActionResult Index() + { + //ViewBag.Description = _systemService.GetDescription(); + + return View(); + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Dockerfile b/Admin.NET/Admin.NET.Web.Entry/Dockerfile new file mode 100644 index 00000000..8fc03565 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 5005 + +COPY . . + +# 设置语言/区域设置环境变量 +ENV LANG zh-Hans + +# 使用阿里云的镜像源进行更新 +RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/' /etc/apt/sources.list + +# 更新包管理器并安装free命令 +RUN apt-get update && apt-get install -y procps + +ENTRYPOINT ["dotnet", "Admin.NET.Web.Entry.dll"] \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/FakeStartup.cs b/Admin.NET/Admin.NET.Web.Entry/FakeStartup.cs new file mode 100644 index 00000000..29914ca2 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/FakeStartup.cs @@ -0,0 +1,14 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Web.Entry; + +/// +/// 供集成测试使用 +/// +public class FakeStartup +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/GeoLite2-City.mmdb b/Admin.NET/Admin.NET.Web.Entry/GeoLite2-City.mmdb new file mode 100644 index 00000000..f5e23dc4 Binary files /dev/null and b/Admin.NET/Admin.NET.Web.Entry/GeoLite2-City.mmdb differ diff --git a/Admin.NET/Admin.NET.Web.Entry/Program.cs b/Admin.NET/Admin.NET.Web.Entry/Program.cs new file mode 100644 index 00000000..2031b3d6 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Program.cs @@ -0,0 +1,27 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +Serve.Run(RunOptions.Default.AddWebComponent()); + +public class WebComponent : IWebComponent +{ + public void Load(WebApplicationBuilder builder, ComponentContext componentContext) + { + // 设置日志过滤 + builder.Logging.AddFilter((provider, category, logLevel) => + { + return !new[] { "Microsoft.Hosting", "Microsoft.AspNetCore" }.Any(u => category.StartsWith(u)) && logLevel >= LogLevel.Information; + }); + + // 设置接口超时时间和上传大小 + builder.Configuration.Get().ConfigureKestrel(u => + { + u.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30); + u.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(30); + u.Limits.MaxRequestBodySize = null; + }); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Properties/launchSettings.json b/Admin.NET/Admin.NET.Web.Entry/Properties/launchSettings.json new file mode 100644 index 00000000..8d4f779c --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Properties/launchSettings.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5005" + // "sslPort": 44325 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Admin.NET.Web.Entry": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "", + "applicationUrl": "http://localhost:5005", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.en.resx b/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.en.resx new file mode 100644 index 00000000..d5554406 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.en.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Admin NET General Permission Development Platform + + + API Interfaces + + + Source Address + + \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.zh-CN.resx b/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.zh-CN.resx new file mode 100644 index 00000000..4c258521 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Resources/Lang.zh-CN.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Admin.NET 通用权限开发平台 + + + API 接口 + + + 源码地址 + + \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/SingleFilePublish.cs b/Admin.NET/Admin.NET.Web.Entry/SingleFilePublish.cs new file mode 100644 index 00000000..7d1392e6 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/SingleFilePublish.cs @@ -0,0 +1,43 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Furion; +using System.Reflection; + +namespace Admin.NET.Web.Entry; + +/// +/// 解决单文件发布问题 +/// +public class SingleFilePublish : ISingleFilePublish +{ + /// + /// 解决单文件不能扫描的程序集 + /// + /// 可同时配置 + /// + public Assembly[] IncludeAssemblies() + { + // 需要 Furion 框架扫描哪些程序集就写上去即可 + return Array.Empty(); + } + + /// + /// 解决单文件不能扫描的程序集名称 + /// + /// 可同时配置 + /// + public string[] IncludeAssemblyNames() + { + // 需要 Furion 框架扫描哪些程序集就写上去即可 + return new[] + { + "Admin.NET.Application", + "Admin.NET.Core", + "Admin.NET.Web.Core", + }; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Views/Home/Index.cshtml b/Admin.NET/Admin.NET.Web.Entry/Views/Home/Index.cshtml new file mode 100644 index 00000000..600408c9 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Views/Home/Index.cshtml @@ -0,0 +1,17 @@ +@{ + ViewData["Title"] = ViewBag.Description; +} + +
+ +
Admin.NET
+
+

+ star + fork + license +

+
+

@ViewBag.Description

+

源码地址

+
\ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Views/Shared/_Layout.cshtml b/Admin.NET/Admin.NET.Web.Entry/Views/Shared/_Layout.cshtml new file mode 100644 index 00000000..af541254 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Views/Shared/_Layout.cshtml @@ -0,0 +1,11 @@ + + + + + + @ViewData["Title"] - Admin.NET + + + @RenderBody() + + \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Views/_ViewImports.cshtml b/Admin.NET/Admin.NET.Web.Entry/Views/_ViewImports.cshtml new file mode 100644 index 00000000..ae2e7b9e --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using Admin.NET.Web.Entry +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/Views/_ViewStart.cshtml b/Admin.NET/Admin.NET.Web.Entry/Views/_ViewStart.cshtml new file mode 100644 index 00000000..1af6e494 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/appsettings.Development.json b/Admin.NET/Admin.NET.Web.Entry/appsettings.Development.json new file mode 100644 index 00000000..c072d2b0 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/appsettings.Development.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "ConfigurationScanDirectories": [ "Configuration", "" ] +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/appsettings.json b/Admin.NET/Admin.NET.Web.Entry/appsettings.json new file mode 100644 index 00000000..cd1c6749 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/appsettings.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", + + "ConfigurationScanDirectories": [ "Configuration", "" ] // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件) +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/ip2region.db b/Admin.NET/Admin.NET.Web.Entry/ip2region.db new file mode 100644 index 00000000..6cf58efb Binary files /dev/null and b/Admin.NET/Admin.NET.Web.Entry/ip2region.db differ diff --git a/Admin.NET/Admin.NET.Web.Entry/sensitive-words.txt b/Admin.NET/Admin.NET.Web.Entry/sensitive-words.txt new file mode 100644 index 00000000..901a560b --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/sensitive-words.txt @@ -0,0 +1,4 @@ +装X|特么的|SB|屌爆了|你妹|马勒戈壁|蛋疼|买了个表|妈蛋|日了狗 +吃翔 +装13 +屁民 diff --git a/Admin.NET/Admin.NET.Web.Entry/web.config b/Admin.NET/Admin.NET.Web.Entry/web.config new file mode 100644 index 00000000..002dc984 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/web.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Dto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Dto.cs.vm new file mode 100644 index 00000000..e143e2b7 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Dto.cs.vm @@ -0,0 +1,30 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace @Model.NameSpace; + + /// + /// @(@Model.BusName)输出参数 + /// + public class @(@Model.ClassName)Dto + { +@foreach (var column in Model.TableField){ +if(@column.EffectType == "fk" && @column.FkEntityName != "" && @column.FkColumnName != ""){ + @:/// + @:/// @column.ColumnComment + @:/// + @:public @(@column.FkColumnNetType) @(@column.PropertyName)@(@column.FkColumnName) { get; set; } + @: +} +} +@foreach (var column in Model.TableField){ + @:/// + @:/// @column.ColumnComment + @:/// + @:public @column.NetType @column.PropertyName { get; set; } + @: +} + } diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Entity.cs.vm new file mode 100644 index 00000000..e4baf1fa --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Entity.cs.vm @@ -0,0 +1,97 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +@if(@Model.BaseClassName!=""){ +@:using Admin.NET.Core; +} +namespace @Model.NameSpace; + +/// +/// @(@Model.Description) +/// +[SugarTable("@(@Model.TableName)","@(@Model.Description)")] +[Tenant("@(@Model.ConfigId)")] +public class @(@Model.EntityName) @Model.BaseClassName +{ +@foreach (var column in Model.TableField){ +if(@Model.BaseClassName=="" && @column.IsPrimarykey){ + @:/// + @:/// @column.ColumnDescription + @:/// + if(!@column.IsNullable){ + @:[Required] + } + if(@column.DataType=="string"||@column.DataType=="string?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true, Length = @column.Length)] + } + else if(@column.DataType=="decimal"||@column.DataType=="decimal?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true, Length = @column.Length, DecimalDigits=@column.DecimalDigits )] + } + else{ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true)] + } + @:public @column.DataType @column.PropertyName { get; set; } + @: +} +else if(@Model.BaseClassName=="" && !@column.IsPrimarykey){ + @:/// + @:/// @column.ColumnDescription + @:/// + if(!@column.IsNullable){ + @:[Required] + } + if(@column.DataType=="string"||@column.DataType=="string?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription", Length = @column.Length)] + } + else if(@column.DataType=="decimal"||@column.DataType=="decimal?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription", Length = @column.Length, DecimalDigits=@column.DecimalDigits )] + } + else{ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription")] + } + @:public @column.DataType @column.PropertyName { get; set; } + @: +} +else if(@Model.BaseClassName!="" && @column.IsPrimarykey && @column.DbColumnName.ToLower()!="id"){ + @:/// + @:/// @column.ColumnDescription + @:/// + if(!@column.IsNullable){ + @:[Required] + } + if(@column.DataType=="string"||@column.DataType=="string?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true, Length = @column.Length)] + } + else if(@column.DataType=="decimal"||@column.DataType=="decimal?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true, Length = @column.Length, DecimalDigits=@column.DecimalDigits )] + } + else{ + @:[SugarColumn(ColumnName = "@column.DbColumnName", IsIdentity = @column.IsIdentity.ToString().ToLower(), ColumnDescription = "@column.ColumnDescription", IsPrimaryKey = true)] + } + @:public @column.DataType @column.PropertyName { get; set; } + @: +} +else if(@Model.BaseClassName!="" && !@column.IsPrimarykey && @column.DbColumnName.ToLower()!="id"){ + @:/// + @:/// @column.ColumnDescription + @:/// + if(!@column.IsNullable){ + @:[Required] + } + if(@column.DataType=="string"||@column.DataType=="string?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription", Length = @column.Length)] + } + else if(@column.DataType=="decimal"||@column.DataType=="decimal?"){ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription", Length = @column.Length, DecimalDigits=@column.DecimalDigits )] + } + else{ + @:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription")] + } + @:public @column.DataType @column.PropertyName { get; set; } + @: +} +} +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Input.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Input.cs.vm new file mode 100644 index 00000000..9469a385 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Input.cs.vm @@ -0,0 +1,121 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core; +using System.ComponentModel.DataAnnotations; + +namespace @Model.NameSpace; + + /// + /// @(@Model.BusName)基础输入参数 + /// + public class @(@Model.ClassName)BaseInput + { +@foreach (var column in Model.TableField){ +if (@column.ColumnKey != "True"){ + + @:/// + @:/// @column.ColumnComment + @:/// + @:public virtual @column.NetType @column.PropertyName { get; set; } + @: +} +} + } + + /// + /// @(@Model.BusName)分页查询输入参数 + /// + public class @(@Model.ClassName)Input : BasePageInput + { + /// + /// 关键字查询 + /// + public string? SearchKey { get; set; } + +@foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y"){ + + @:/// + @:/// @column.ColumnComment + @:/// + if(@column.NetType?.EndsWith("?") == true){ + @:public @column.NetType @column.PropertyName { get; set; } + }else { + @:public @(@column.NetType)? @column.PropertyName { get; set; } + } + @: + + if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:/// + @: /// @(@column.ColumnComment)范围 + @: /// + @: public DateTime?[] @(@column.PropertyName)Range { get; set; } + +} + +} +} + } + + /// + /// @(@Model.BusName)增加输入参数 + /// + public class Add@(@Model.ClassName)Input : @(@Model.ClassName)BaseInput + { +@foreach (var column in Model.TableField){ +if (@column.WhetherRequired == "Y"){ + @:/// + @:/// @column.ColumnComment + @:/// + @:[Required(ErrorMessage = "@(@column.ColumnComment)不能为空")] + @:public override @column.NetType @column.PropertyName { get; set; } + @: +} +} + } + + /// + /// @(@Model.BusName)删除输入参数 + /// + public class Delete@(@Model.ClassName)Input : BaseIdInput + { +@foreach (var column in Model.TableField){ +if (@column.ColumnKey == "True" && @column.PropertyName != "Id"){ + @:/// + @:/// @column.ColumnComment + @:/// + @:[Required(ErrorMessage = "@(@column.ColumnComment)不能为空")] + @:public @column.NetType @column.PropertyName { get; set; } + @: +} +} + } + + /// + /// @(@Model.BusName)更新输入参数 + /// + public class Update@(@Model.ClassName)Input : @(@Model.ClassName)BaseInput + { +@foreach (var column in Model.TableField){ +if (@column.ColumnKey == "True"){ + @:/// + @:/// @column.ColumnComment + @:/// + @:[Required(ErrorMessage = "@(@column.ColumnComment)不能为空")] + @:public @column.NetType @column.PropertyName { get; set; } + @: +} +} + } + + /// + /// @(@Model.BusName)主键查询输入参数 + /// + public class QueryById@(@Model.ClassName)Input : Delete@(@Model.ClassName)Input + { + + } diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Manage.js.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Manage.js.vm new file mode 100644 index 00000000..fd7d5b4f --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Manage.js.vm @@ -0,0 +1,116 @@ +@{Dictionary definedObjects = new Dictionary();} +import request from '/@@/utils/request'; +enum Api { + Add@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/add', + Delete@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/delete', + Update@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/update', + Page@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/page', + Detail@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/detail', + @foreach (var column in Model.TableField){ +if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ + @:Get@(@column.FkEntityName)@(@column.PropertyName)Dropdown = '/api/@(@Model.LowerClassName)/@(@column.FkEntityName)@(@column.PropertyName)Dropdown', +}else if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("Get@(@column.FkEntityName)Tree")){ + @{definedObjects.Add("Get@(@column.FkEntityName)Tree", 1);} + @:Get@(@column.FkEntityName)Tree = '/api/@(@Model.LowerClassName)/@(@column.FkEntityName)Tree', +}else if(@column.EffectType == "Upload"){ + @:Upload@(@column.PropertyName) = '/api/@(@Model.LowerClassName)/Upload@(@column.PropertyName)', +} +} +} + +// 增加@(@Model.BusName) +export const add@(@Model.ClassName) = (params?: any) => + request({ + url: Api.Add@(@Model.ClassName), + method: 'post', + data: params, + }); + +// 删除@(@Model.BusName) +export const delete@(@Model.ClassName) = (params?: any) => + request({ + url: Api.Delete@(@Model.ClassName), + method: 'post', + data: params, + }); + +// 编辑@(@Model.BusName) +export const update@(@Model.ClassName) = (params?: any) => + request({ + url: Api.Update@(@Model.ClassName), + method: 'post', + data: params, + }); + +// 分页查询@(@Model.BusName) +export const page@(@Model.ClassName) = (params?: any) => + request({ + url: Api.Page@(@Model.ClassName), + method: 'post', + data: params, + }); + +// 详情@(@Model.BusName) +export const detail@(@Model.ClassName) = (id: any) => + request({ + url: Api.Detail@(@Model.ClassName), + method: 'get', + data: { id }, + }); + +@foreach (var column in Model.TableField){ + if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ +@:export const get@(@column.FkEntityName)@(@column.PropertyName)Dropdown = () => + @:request({ + @:url: Api.Get@(@column.FkEntityName)@(@column.PropertyName)Dropdown, + @:method: 'get' + @:}); + }else if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("get@(@column.FkEntityName)Tree")){ + @{definedObjects.Add("get@(@column.FkEntityName)Tree", 1);} +@:export const get@(@column.FkEntityName)Tree = () => + @:request({ + @:url: Api.Get@(@column.FkEntityName)Tree, + @:method: 'get' + @:}); + }else if(@column.EffectType == "Upload"){ + + @:/** + @:* 上传@(@column.ColumnComment) + @:*/ +@:export const upload@(@column.PropertyName) = (params: any) => + @:uploadFileHandle(params, Api.Upload@(@column.PropertyName)) + } +} + +@foreach (var column in Model.TableField){ + if(@column.EffectType == "Upload"){ + @:export const uploadFileHandle = (params: any, url: string) => { + @:const formData = new window.FormData(); + @:formData.append('file', params.file); + @://自定义参数 + @:if (params.data) { + @:Object.keys(params.data).forEach((key) => { + @:const value = params.data![key]; + @:if (Array.isArray(value)) { + @:value.forEach((item) => { + @:formData.append(`${key}[]`, item); + @:}); + @:return; + @:} + @:formData.append(key, params.data![key]); + @:}); + @:} + @:return request({ + @:url: url, + @:method: 'POST', + @:data: formData, + @:headers: { + @:'Content-type': 'multipart/form-data;charset=UTF-8', + @:// ts-ignore + @:ignoreCancelToken: true, + @:}, + @:}); +@:}; + break; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Output.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Output.cs.vm new file mode 100644 index 00000000..40e8b988 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Output.cs.vm @@ -0,0 +1,64 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace @Model.NameSpace; + +/// +/// @(@Model.BusName)输出参数 +/// +public class @(@Model.ClassName)Output +{ +@foreach (var column in Model.TableField){ + @:/// + @:/// @column.ColumnComment + @:/// +if(column.EffectType == "fk") +{ + @:public @column.NetType @column.PropertyName { get; set; } + @: + @:/// + @:/// @(column.ColumnComment) 描述 + @:/// + @:public @(@column.FkColumnNetType) @(@column.PropertyName)@(@column.FkColumnName) { get; set; } + +}else if(column.EffectType == "Upload"){ + @:public @column.NetType @column.PropertyName { get; set; } + @:public SysFile @(@column.PropertyName)Attachment { get; set; } +}else if(column.EffectType == "ApiTreeSelect"){ + @:public @column.NetType @column.PropertyName { get; set; } + @: + @:/// + @:/// @(column.ColumnComment) 描述 + @:/// + @:public string? @(@column.PropertyName)@(@column.DisplayColumn) { get; set; } +}else{ + @:public @column.NetType @(@column.PropertyName) { get; set; } +} + @: +} + } + + +@foreach (var column in Model.TableField){ +if (@column.EffectType == "ApiTreeSelect"){ + @:// 使用实际实体@(@column.FkTableName),所以这里就删了 + @:/* + @:[SugarTable("@(@column.FkTableName)")] + @:public class @(@column.FkEntityName)TreeOutput: EntityBaseId + @:{ + @:[SugarColumn(ColumnName = "@(@column.DisplayColumn)")] + @:public @(@Model.GetColumnNetType(@column.FkTableName,@column.DisplayColumn)) Label { get; set; } + + @:[SugarColumn(ColumnName = "@(@column.ValueColumn)", IsPrimaryKey = true, IsIdentity = false)] + @:public @(@Model.GetColumnNetType(@column.FkTableName,@column.ValueColumn)) Value { get; set; } + + @:public @(@Model.GetColumnNetType(@column.FkTableName,@column.PidColumn)) @column.PidColumn { get; set; } + @:[SugarColumn(IsIgnore = true)] + @:public List<@(@column.FkEntityName)TreeOutput> Children { get; set; } + @:} + @:*/ +} +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/SeedData.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/SeedData.cs.vm new file mode 100644 index 00000000..7107024b --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/SeedData.cs.vm @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Admin.NET.Core; +using @Model.EntityNameSpace; + +namespace @Model.NameSpace; + +/// +/// @(Model.Description) 表种子数据 +/// +public class @(Model.SeedDataName): ISqlSugarEntitySeedData<@(Model.EntityName)> +{ + /// + /// 种子数据 + /// + /// + public IEnumerable<@(Model.EntityName)> HasData() + { + string recordsJSON = @@" + @(Model.RecordsJSON.Replace("\"","\"\"").Replace("\n", "\n\t\t\t")) + "; + List<@(Model.EntityName)> records = Newtonsoft.Json.JsonConvert.DeserializeObject>(recordsJSON); + @if (Model.JsonIgnoreInfo.Count>0) { + @: + @:#region 处理 JsonIgnore 的Property + @: + @foreach (var jii in Model.JsonIgnoreInfo){ + @foreach (var j in jii){ + @:records[@j.RecordIndex].@(j.Name) = @(j.Value); + } + @: + } + @:#endregion + } + + // 后处理数据的特殊字段 + //for (int i = 0; i < records.Count; i++) { } + + return records; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm new file mode 100644 index 00000000..a42bda89 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm @@ -0,0 +1,231 @@ +using Admin.NET.Core.Service; +using @(@Model.NameSpace).Entity; +using Microsoft.AspNetCore.Http; +@{ + string joinTableName = "u"; + Dictionary definedObjects = new Dictionary(); + bool haveLikeCdt = false; + foreach (var column in Model.TableField){ + if (column.QueryWhether == "Y" && column.QueryType == "like"){ + haveLikeCdt = true; + } + } +} +namespace @Model.NameSpace; +/// +/// @(@Model.BusName)服务 +/// +[ApiDescriptionSettings(@(@Model.ProjectLastName)Const.GroupName, Order = 100)] +public class @(@Model.ClassName)Service : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository<@(@Model.ClassName)> _rep; + public @(@Model.ClassName)Service(SqlSugarRepository<@(@Model.ClassName)> rep) + { + _rep = rep; + } + + /// + /// 分页查询@(@Model.BusName) + /// + /// + /// + [HttpPost] + [ApiDescriptionSettings(Name = "Page")] + public async Task> Page(@(@Model.ClassName)Input input) + { +@if (haveLikeCdt) { + @:input.SearchKey = input.SearchKey?.Trim(); +} + var query = _rep.AsQueryable() +@{string conditionFlag = "";} +@if (haveLikeCdt) { + @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y" && column.QueryType == "like"){ + @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) + conditionFlag="|| "; + } + } + @:) +} +@foreach (var column in Model.TableField){ +if (@column.QueryWhether == "Y"){ + if (@column.NetType?.TrimEnd('?') == "string"){ + if(@column.QueryType == "like"){ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) + }else{ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ + @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) + }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { + @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } +} +} +@if(Model.IsJoinTable){ + @://处理外键和TreeSelector相关字段的连接 + @foreach (var column in Model.TableField){ + if(@column.EffectType == "fk"){ + joinTableName += ", " + column.PropertyName.ToLower(); + @:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).Id ) + } else if(@column.EffectType == "ApiTreeSelect"){ + joinTableName += ", " + column.PropertyName.ToLower(); + @:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).@(@column.ValueColumn) ) + } + } + @:.OrderBy(u => u.CreateTime) + @:.Select((@(@joinTableName)) => new @(@Model.ClassName)Output + @:{ +@foreach (var column in Model.TableField){ + if(@column.EffectType == "fk"){ + @:@(@column.PropertyName) = u.@(@column.PropertyName), + @:@(@column.PropertyName)@(@column.FkColumnName) = @(@column.PropertyName.ToLower()).@(@column.FkColumnName), + } else if(@column.EffectType == "ApiTreeSelect"){ + @:@(@column.PropertyName) = u.@(@column.PropertyName), + @:@(@column.PropertyName)@(@column.DisplayColumn) = @(@column.PropertyName.ToLower()).@(@column.DisplayColumn), + } else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true){ + @:@(@column.PropertyName) = (@(@column.NetType))u.@(@column.PropertyName), + } else { + @:@(@column.PropertyName) = u.@(@column.PropertyName), + } +} + @:}); +@foreach (var column in Model.TableField){ + if(@column.EffectType == "fk"){ + + }else if(@column.EffectType == "Upload"){ + @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) + } +} +} else { + @:.Select<@(@Model.ClassName)Output>(); +} + return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加@(@Model.BusName) + /// + /// + /// + [HttpPost] + [ApiDescriptionSettings(Name = "Add")] + public async Task Add(Add@(@Model.ClassName)Input input) + { + var entity = input.Adapt<@(@Model.ClassName)>(); + await _rep.InsertAsync(entity); + return entity.Id; + } + + /// + /// 删除@(@Model.BusName) + /// + /// + /// + [HttpPost] + [ApiDescriptionSettings(Name = "Delete")] + public async Task Delete(Delete@(@Model.ClassName)Input input) + { +@foreach (var column in Model.TableField){ +if (@column.ColumnKey == "True"){ + @:var entity = await _rep.GetFirstAsync(u => u.@(@column.PropertyName) == input.@(@column.PropertyName)) ?? throw Oops.Oh(ErrorCodeEnum.D1002); +} +} + await _rep.FakeDeleteAsync(entity); //假删除 + //await _rep.DeleteAsync(entity); //真删除 + } + + /// + /// 更新@(@Model.BusName) + /// + /// + /// + [HttpPost] + [ApiDescriptionSettings(Name = "Update")] + public async Task Update(Update@(@Model.ClassName)Input input) + { + var entity = input.Adapt<@(@Model.ClassName)>(); + await _rep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); + } + + /// + /// 获取@(@Model.BusName) + /// + /// + /// + [HttpGet] + [ApiDescriptionSettings(Name = "Detail")] + public async Task<@(@Model.ClassName)> Detail([FromQuery] QueryById@(@Model.ClassName)Input input) + { +@foreach (var column in Model.TableField){ +if (@column.ColumnKey == "True"){ + @:return await _rep.GetFirstAsync(u => u.@(@column.PropertyName) == input.@(@column.PropertyName)); +} +} + } + + /// + /// 获取@(@Model.BusName)列表 + /// + /// + /// + [HttpGet] + [ApiDescriptionSettings(Name = "List")] + public async Task> List([FromQuery] @(@Model.ClassName)Input input) + { + return await _rep.AsQueryable().Select<@(@Model.ClassName)Output>().ToListAsync(); + } + +@foreach (var column in Model.TableField){ +if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ + @:/// + @:/// 获取@(@column.ColumnComment)列表 + @:/// + @:/// + @:/// + @:[ApiDescriptionSettings(Name = "@(@column.FkEntityName)@(@column.PropertyName)Dropdown"), HttpGet] + @:public async Task @(@column.FkEntityName)@(@column.PropertyName)Dropdown() + @:{ + @:return await _rep.Context.Queryable<@(@column.FkEntityName)>() + @:.Select(u => new + @:{ + @:Label = u.@(@column.FkColumnName), + @:Value = u.Id + @:} + @:).ToListAsync(); + @:} +} +} + +@foreach (var column in Model.TableField){ +if(@column.EffectType == "Upload"){ + @:/// + @:/// 上传@(@column.ColumnComment) + @:/// + @:/// + @:/// + @:[ApiDescriptionSettings(Name = "Upload@(@column.PropertyName)"), HttpPost] + @:public async Task Upload@(@column.PropertyName)([Required] IFormFile file) + @:{ + @:var service = App.GetRequiredService(); + @:return await service.UploadFile(new FileUploadInput { File = file, Path = "upload/@(@column.PropertyName)" }); + @:} +} +} + + +@foreach (var column in Model.TableField){ +if(@column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(@column.FkEntityName)Tree")){ + @{definedObjects.Add("@(@column.FkEntityName)Tree", 1);} + @:[HttpGet("@(@column.FkEntityName)Tree")] + @:public async Task @(@column.FkEntityName)Tree() + @:{ + @:return await _rep.Context.Queryable<@(@column.FkEntityName)>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0); + @:} +} +} + +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/data.data.ts.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/data.data.ts.vm new file mode 100644 index 00000000..f404ea20 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/data.data.ts.vm @@ -0,0 +1,161 @@ +import { h } from 'vue'; +import { BasicColumn, FormSchema } from '/@@/components/Table'; +@foreach (var column in Model.TableField){ +if(@column.EffectType == "Upload"){ +@:import { uploadFile } from '/@@/api/sys/admin'; +}else if(@column.EffectType == "fk"){ +@:import { get@(@column.FkEntityName)Dropdown } from '/@@/api/main/@(@Model.ClassName)'; +}else if(@column.EffectType == "Select"){ +@:import { getDataList } from '/@@/api/sys/admin'; +}else if(@column.EffectType == "ApiTreeSelect"){ +@:import { get@(@column.FkEntityName)Tree } from '/@@/api/main/@(@Model.ClassName)'; +}else if(@column.EffectType == "ConstSelector"){ +@:import { codeToName, getSelector } from '/@@/utils/helper/constSelectorHelper'; +}else if(@column.EffectType == "Switch"){ +@:import { Switch } from 'ant-design-vue'; +} +} +export const columns: BasicColumn[] = [ + @foreach (var column in Model.TableField){ + if(@column.WhetherTable == "Y"){ + @:{ + @:title: '@column.ColumnComment', + @:dataIndex: '@column.LowerPropertyName', + @:sorter: true, +if(@column.EffectType == "Upload"){ + @:slots: { customRender: '@(@column.LowerPropertyName)' }, +}else if(@column.EffectType == "fk"){ + @:customRender: ({ record }) => { + @:return record.fk@(@column.PropertyName).@(@column.LowerFkColumnName); + @:}, +}else if(@column.EffectType == "Switch"){ + @:customRender: ({ record }) => { + @:return h(@(@column.EffectType), { checked: record.@(@column.LowerPropertyName) }); + @:}, +}else if(@column.EffectType == "ConstSelector"){ + @:customRender: ({ record }) => { + @:return codeToName(record.@(@column.LowerPropertyName), '@(@column.DictTypeCode)'); + @:}, +} + @:}, + } + + } +]; + +export const searchFormSchema: FormSchema[] = [ +@foreach (var column in Model.QueryWhetherList){ + @:{ + @:field: '@column.LowerPropertyName', + @:label: '@column.ColumnComment', + @:colProps: { span: 8 }, +if(@column.EffectType == "fk"){ + @:component: 'ApiSelect', + @:componentProps: { + @:api: get@(@column.FkEntityName)Dropdown, + @:labelField: 'label', + @:valueField: 'value', + @:}, +}else if(@column.EffectType == "Select"){ + @:component: 'ApiSelect', + @:componentProps: { + @:api: getDataList, + @:params: '@(@column.DictTypeCode)', + @:fieldNames: { + @:label: 'label', + @:value: 'value', + @:}, + @:}, +}else if(@column.EffectType == "ConstSelector"){ + @:component: 'Select', + @:componentProps: { + @:options: getSelector('@(@column.DictTypeCode)'), + @:fieldNames: { + @:label: 'name', + @:value: 'code', + @:}, + @:}, +}else if(@column.EffectType == "ApiTreeSelect"){ + @:component: '@(@column.EffectType)', + @:componentProps: { + @:api: get@(@column.FkEntityName)Tree, + @:}, +} +else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:component: 'RangePicker', + @:componentProps: { + @: valueFormat:"YYYY-MM-DD" + @:}, +} +else{ + @:component: 'Input', +} + + @:}, +} +]; + +export const formSchema: FormSchema[] = [ + @foreach (var column in Model.TableField){ + @:{ + @:label: '@column.ColumnComment', + @:field: '@column.LowerPropertyName', +if(@column.EffectType == "fk"){ + @:component: 'ApiSelect', + @:componentProps: { + @:api: get@(@column.FkEntityName)Dropdown, + @:labelField: 'label', + @:valueField: 'value', + @:}, +}else if(@column.EffectType == "Select"){ + @:component: 'ApiSelect', + @:componentProps: { + @:api: getDataList, + @:params: '@(@column.DictTypeCode)', + @:fieldNames: { + @:label: 'label', + @:value: 'value', + @:}, + @:}, +}else if(@column.EffectType == "ConstSelector"){ + @:component: 'Select', + @:componentProps: { + @:options: getSelector('@(@column.DictTypeCode)'), + @:fieldNames: { + @:label: 'name', + @:value: 'code', + @:}, + @:}, +}else if(@column.EffectType == "ApiTreeSelect"){ + @:component: '@(@column.EffectType)', + @:componentProps: { + @:api: get@(@column.FkEntityName)Tree, + @:}, +}else if(@column.EffectType == "Switch"){ + @:component: '@(@column.EffectType)', + @:componentProps: { + @:checkedChildren: '是', + @:unCheckedChildren: '否', + @:}, +}else{ + @:component: '@column.EffectType', +} + if(@column.WhetherRequired == "Y"){ + @:required: true, + }else{ + @:required: false, + } + if(@column.EffectType == "Upload"){ + @:componentProps: { + @:maxNumber: 1, + @:api: uploadFile, + @:}, + } + if(@column.LowerPropertyName == "id"){ + @:show: false, + } + @:colProps: { span: 12 }, + @:}, + + } +]; diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/dataModal.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/dataModal.vue.vm new file mode 100644 index 00000000..25db585d --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/dataModal.vue.vm @@ -0,0 +1,71 @@ + + diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm new file mode 100644 index 00000000..2fe982e8 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm @@ -0,0 +1,337 @@ +@{Dictionary definedObjects = new Dictionary();} +@{var pkField = Model.TableField.Where(c => c.ColumnKey == "True").FirstOrDefault();} +@{string pkFieldName = LowerFirstLetter(pkField.PropertyName);} + + + + + + + +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm new file mode 100644 index 00000000..a9b55298 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm @@ -0,0 +1,377 @@ +@{ + var pkField = Model.TableField.Where(c => c.ColumnKey == "True").FirstOrDefault(); + string pkFieldName = null; + if(pkField != null && !string.IsNullOrEmpty(pkField.PropertyName)) + { + pkFieldName = LowerFirstLetter(pkField.PropertyName); + } + Dictionary definedObjects = new Dictionary(); + bool haveLikeCdt = false; + foreach (var column in Model.TableField){ + if (column.QueryWhether == "Y" && column.QueryType == "like"){ + haveLikeCdt = true; + } + } +} +