commit 1dedb0586f7bc441e21014cc8af0e5ce9c407ade Author: aixiaojun <512535197@qq.com> Date: Thu Jul 17 09:35:54 2025 +0800 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/.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/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fb55385 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,186 @@ +[*.cs] + +# SA1200: Using directives should be placed correctly +dotnet_diagnostic.SA1200.severity = none +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 = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion + +[*.{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_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_allow_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 +tab_width = 4 +indent_size = 4 +dotnet_style_operator_placement_when_wrapping = beginning_of_line +[*.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_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = 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_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_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 + +# SA1202: Elements should be ordered by access +dotnet_diagnostic.SA1202.severity = none + +[*.vb] +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion +dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface +dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 + +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.类型.required_modifiers = + +dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method +dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.非字段成员.required_modifiers = + +# 命名样式 + +dotnet_naming_style.以_i_开始.required_prefix = I +dotnet_naming_style.以_i_开始.required_suffix = +dotnet_naming_style.以_i_开始.word_separator = +dotnet_naming_style.以_i_开始.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ff1146 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +################################################################################ +# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 +################################################################################ + +bin +obj +.vs +Debug + +/BinaLiangHua/NPlatform.API/obj +/BinaLiangHua/NPlatform.API/bin +/BinaLiangHua/NPlatform.API/obj +dist +node_modules +/BinaLiangHua/NPlatform.API/wwwroot +/doc/db.pdb +/IdentityServer/vue-next-admin/node_modules.zip +/BZPT.IdentityServer/BZPT.IdentityServer.csproj.user +/BZPT.IdentityServer/appsettings.Development.json +/BZPT.Api/appsettings.Development.json +/BZPT.Api/BZPT.Api.csproj.user +*.user +/BZPT.Api/BZPT.API.csproj +/BZPT.Api/BZPT.API.csproj +/BZPT.Api/BZPT.API.csproj +/BZPT.Api/appsettings.Develop.json +/BZPT.Api/Temporary_Upload +/BZPT.Api/Upload +*_Upload +/BZPT.Api/logs +logs \ No newline at end of file diff --git a/BZPT.Api/.config/dotnet-tools.json b/BZPT.Api/.config/dotnet-tools.json new file mode 100644 index 0000000..558293e --- /dev/null +++ b/BZPT.Api/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "7.0.10", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/BZPT.Api/Dockerfile b/BZPT.Api/Dockerfile new file mode 100644 index 0000000..f890d8a --- /dev/null +++ b/BZPT.Api/Dockerfile @@ -0,0 +1,37 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM registry.cn-hangzhou.aliyuncs.com/newbe36524/aspnet:8.0 AS base +USER root +#定义时区参数 +ENV TZ=Asia/Shanghai +#设置编码 +ENV LANG C.UTF-8 +WORKDIR /app +EXPOSE 19901 +EXPOSE 443 + +FROM registry.cn-hangzhou.aliyuncs.com/newbe36524/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["BZPT.Api/BZPT.Api.csproj", "BZPT.Api/"] +COPY ["BZPT.Domains/BZPT.Domains.csproj", "BZPT.Domains/"] +COPY ["BZPT.DTO/BZPT.DTO.csproj", "BZPT.DTO/"] +COPY ["BZPT.SqlSugarRepository/BZPT.SqlSugarRepository.csproj", "BZPT.SqlSugarRepository/"] +RUN dotnet restore "BZPT.Api/BZPT.Api.csproj" +COPY . . +WORKDIR "/src/BZPT.Api" + +RUN dotnet build "BZPT.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +WORKDIR "/src/BZPT.Api" +RUN dotnet publish "BZPT.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +#时区设置1 +RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone + +ENTRYPOINT ["dotnet", "BZPT.Api.dll"] \ No newline at end of file diff --git a/BZPT.Api/Program.cs b/BZPT.Api/Program.cs new file mode 100644 index 0000000..43f912b --- /dev/null +++ b/BZPT.Api/Program.cs @@ -0,0 +1,455 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using NPlatform.Infrastructure.Config; +using NPlatform.Middleware; +using NPlatform.API; +using NPlatform.DI; +using NPlatform.Repositories; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using SqlSugar; +using IGeekFan.AspNetCore.Knife4jUI; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using IdentityServer4.Configuration; +using System.Security.Cryptography.X509Certificates; +using BZPT.Repositories; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NPlatform.Infrastructure.Config.Section; +using ServiceStack; +using Microsoft.IdentityModel.Tokens; +using Hangfire; +using Hangfire.Redis.StackExchange; +using Hangfire.Dashboard.BasicAuthorization; +using BZPT.Domains.Application; +using System.Security.Claims; +using NPlatform.Infrastructure.IdGenerators; +using NPOI.SS.Formula.Functions; +using Microsoft.AspNetCore.DataProtection; +using StackExchange.Redis; +using BZPT.Api.Middleware; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Newtonsoft.Json.Linq; +using Serilog.Context; +using Serilog; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.FileProviders; +using BZPT.Domains.IRepositories; +using BZPT.Domains.IService.Sys; +using BZPT.Api; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpOverrides; + +var builder = WebApplication.CreateBuilder(args); +var serviceConfig = builder.Configuration.GetServiceConfig(); + +builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); +// 替换控制器激活器 +builder.Services.Replace(ServiceDescriptor.Scoped()); + +// 健康检查 +builder.Services.AddHealthChecks().AddCheck(serviceConfig.ServiceName); +// 内存缓存 +builder.Services.AddMemoryCache(); +// Autofac 配置 +builder.Services.AddAutofac(); +var redisConfig = builder.Configuration.GetRedisConfig(); +//从配置获取 Redis 连接字符串 +var redisConn = $"{redisConfig.Connections?.FirstOrDefault()},password={redisConfig.Password}"; +Console.WriteLine("redis 连接:" + redisConn); +// 配置 Hangfire +builder.Services.AddHangfire(x => +{ + x.SetDataCompatibilityLevel(CompatibilityLevel.Version_170) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseRedisStorage(redisConn, new RedisStorageOptions + { + Db = 11, + Prefix = "Hangfire:" + }); +}); + +builder.Services.AddHangfireServer(); + +// 连接到 Redis +var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConn); +// 注册 IConnectionMultiplexer +builder.Services.AddSingleton(connectionMultiplexer); + +// 注册 IDatabase +builder.Services.AddScoped(provider => +{ + var connection = provider.GetRequiredService(); + return connection.GetDatabase(); // 返回 IDatabase 实例 +}); +// 主机配置 +var repositoryOptions = new RepositoryOptions(); +builder.Host.Configure(builder.Configuration, repositoryOptions); + +// 数据库上下文配置 +var dbHost = builder.Configuration["DB_HOST"]; +var dbPort = builder.Configuration["DB_PORT"]; +var dbName = builder.Configuration["DB_NAME"]; +var logDbName = builder.Configuration["LOG_DB_NAME"]; +var dbUser = builder.Configuration["DB_USER"]; +var dbPass = builder.Configuration["DB_PASSWORD"]; + +builder.UseMySerilog($"Server={dbHost};Port={dbPort};DATABASE={logDbName};User Id={dbUser};PWD={dbPass};", serviceConfig.ServiceName); + +//// 配置日志 +//var log = builder.Logging.AddLog4Net(); +//log.AddConsole(); +//log.AddDebug(); +//log.SetMinimumLevel(LogLevel.Debug); + +builder.Services.AddScoped(serviceProvider => +{ + return new DBContext($"Server={dbHost};Port={dbPort};DATABASE={dbName};User Id={dbUser};PWD={dbPass};", DbType.Dm, ConfigId: "default"); +}); +builder.Services.AddScoped(); +// 控制器配置 +builder.Services.AddControllersWithViews(mvcOptions => +{ + mvcOptions.Filters.Remove(mvcOptions.Filters.OfType().FirstOrDefault()); +}).AddJsonOptions(options => +{ + //options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + //options.JsonSerializerOptions.PropertyNameCaseInsensitive = false; // 确保区分大小写 + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new DateTimeConverter("yyyy-MM-dd HH:mm:ss")); +}); + + +// 从 appsettings.json 加载表单限制 +builder.Services.Configure(options => +{ + options.MultipartBodyLengthLimit = + builder.Configuration.GetValue("FormOptions:MultipartBodyLengthLimit"); +}); + + +// API 行为配置 +builder.Services.Configure(options => +{ + options.InvalidModelStateResponseFactory = actionContext => + { + var errors = actionContext.ModelState?.Where(e => e.Value.Errors.Count > 0); + var strError = new StringBuilder(); + foreach (var error in errors) + { + var msg = error.Value.Errors.FirstOrDefault()?.ErrorMessage; + if (!string.IsNullOrWhiteSpace(msg)) + strError.Append(msg); + } + return new FailResult("错误:" + strError.ToString()); + }; +}); + +// IdentityServer4 配置 +var serviceProvider = builder.Services.BuildServiceProvider(); +var dbContext = serviceProvider.GetService(); + +var keyPath = Path.Combine(Environment.CurrentDirectory, builder.Configuration["CERT_PATH"] ?? ""); +Console.WriteLine($"Certificate path: {keyPath}, Exists: {File.Exists(keyPath)}"); + +// CORS 配置 + +builder.Services.AddCors(options => options.AddPolicy("CorsMy", policy => +{ + // 允许特定的源访问 + policy.WithOrigins("*") + .AllowAnyHeader() + .AllowAnyMethod(); + +})); + +if (!string.IsNullOrEmpty(keyPath) && File.Exists(keyPath)) +{ + builder.WebHost.ConfigureKestrel(serverOptions => + { + serverOptions.ConfigureHttpsDefaults(httpsOptions => + { + httpsOptions.ServerCertificate = new X509Certificate2(keyPath, builder.Configuration["CERT_PASSWORD"]); + }); + }); +} +else +{ + Console.WriteLine("⚠️ 证书未找到或环境变量未设置,HTTPS 可能无法正常工作!"); +} + + +Console.WriteLine($"AuthorityServer 配置: {builder.Configuration["AuthorityServer"]}"); +// 配置 Cookie 认证 +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; +}) +.AddJwtBearer(options => +{ + options.Authority = builder.Configuration["AuthorityServer"]; + options.Audience = serviceConfig.ServiceID; // 受众 +#if DEBUG + options.RequireHttpsMetadata = false; // 开发环境允许 HTTP +#else + options.RequireHttpsMetadata = true; // 生产环境强制 HTTPS +#endif + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = true, // 验证 Audience + ValidAudiences =new string[] { serviceConfig.ServiceID }, // 或者 "api.BZPT",取决于你的 API 需要验证哪个 Audience + ValidateIssuer = true, // 验证 Issuer + ValidIssuer = builder.Configuration["AuthorityServer"], // 确保与 IdentityServer4 的 Issuer 配置一致 + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero // 生产环境建议设为小值或 TimeSpan.Zero,防止 Token 刚签发就被认为过期 + }; + options.BackchannelHttpHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true // 忽略 SSL 错误 + }; + + options.Events = new JwtBearerEvents + { + OnAuthenticationFailed = context => + { + Console.WriteLine($"{serviceConfig.ServiceID}|{builder.Configuration["AuthorityServer"]} "); + // 记录身份验证失败的详细信息 + Console.WriteLine($"Authentication failed:{context.Exception.Message}"); + return Task.CompletedTask; + }, + OnTokenValidated = context => + { + // 记录 token 验证成功的信息 + Console.WriteLine("Token validated successfully."); + ////打印 Claims 信息以便调试 + if (context.Principal != null) + { + Console.WriteLine($"Authenticated User Name: {context.Principal.Identity?.Name}"); + foreach (var claim in context.Principal.Claims) + { + Console.WriteLine($" Claim Type: {claim.Type}, Value: {claim.Value}"); + } + } + return Task.CompletedTask; + }, + OnMessageReceived = context => + { + //Console.WriteLine("--- JwtBearer OnMessageReceived triggered ---"); + if (context.Request.Headers.ContainsKey("Authorization")) + { + Console.WriteLine($"Raw Authorization Header: {context.Request.Headers["Authorization"]}"); + } + // 这里的 context.Token 应该已经由 JwtBearerHandler 填充,除非有其他中间件干扰 + Console.WriteLine($"Received token (JwtBearer): {context.Token ?? "NULL"}"); // 确保打印 NULL + Console.WriteLine("--- End JwtBearer OnMessageReceived ---"); + return Task.CompletedTask; + }, + OnChallenge = context => + { + Console.WriteLine("JwtBearer Challenge triggered."); + Console.WriteLine($"Challenge Scheme: {context.Scheme.Name}"); + Console.WriteLine($"Challenge Status Code: {context.Response.StatusCode}"); + if (!string.IsNullOrEmpty(context.Error)) Console.WriteLine($"Challenge Error: {context.Error}"); + if (!string.IsNullOrEmpty(context.ErrorDescription)) Console.WriteLine($"Challenge Error Description: {context.ErrorDescription}"); + //全自定义 401 响应,可以在这里处理 + // context.HandleResponse(); // 阻止默认的 401 行为 + // context.Response.StatusCode = StatusCodes.Status401Unauthorized; + // context.Response.ContentType = "application/json"; + // return context.Response.WriteAsync(JsonSerializer.Serialize(new { message = "Unauthorized", detail = context.ErrorDescription })); + return Task.CompletedTask; + } + }; +}); + +// Swagger 配置 +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc(serviceConfig.ServiceName, new OpenApiInfo { Title = $"{serviceConfig.ServiceName} 接口文档", Version = serviceConfig.ServiceVersion }); + c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "BZPT.Api.xml"), true); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } , + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + }, new List() } + }); + + // 定义 OAuth2 Client Credentials 安全方案 + c.AddSecurityDefinition("OAuth2", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows + { + ClientCredentials = new OpenApiOAuthFlow + { + TokenUrl = new Uri($"{builder.Configuration["AuthorityServer"]}/connect/token"), // IdentityServer4 令牌端点 + Scopes = new Dictionary + { + { "api_scope", "访问 API 的权限" } + } + } + } + }); + c.AddServer(new OpenApiServer { Url = "", Description = "vvv" }); + c.CustomOperationIds(apiDesc => (apiDesc.ActionDescriptor as ControllerActionDescriptor)?.ControllerName + "-" + (apiDesc.ActionDescriptor as ControllerActionDescriptor)?.ActionName); +}); + +var redis = ConnectionMultiplexer.Connect(redisConn); +builder.Services.AddDataProtection() + .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys") + .SetApplicationName(serviceConfig.ServiceID); +builder.Services.AddAntiforgery(options => +{ + // 可以在这里配置防伪令牌的相关选项 + options.HeaderName = "X-CSRF-TOKEN"; +}); + +//将 Serilog 注册到默认日志工厂(非必须,但更兼容) +builder.Logging.ClearProviders(); // 可选:移除默认日志提供程序(如不需要控制台重复输出) +builder.Logging.AddSerilog(dispose: true); // 桥接 Microsoft.Extensions.Logging 到 Serilog + +// 应用构建 +var app = builder.Build(); + +// 并且在 UseAuthentication() 和 UseAuthorization() 之前。 +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost + // 注意:通常不需要 ForwardedHeaders.XForwardedPort,因为 X-Forwarded-Host 包含主机和端口 + // 但如果您的反向代理只设置了 X-Forwarded-Port,则可能需要添加 + + // !!!重要:如果您的反向代理是内部的,并且其IP是固定的,建议指定 KnownProxies 或 KnownNetworks, + // 以防止伪造的 X-Forwarded-* 头。如果这是在容器内部,且代理是 Ingress Controller 或 Docker 自己的网络, + // 通常可以安全地跳过 KnownProxies/Networks,但在生产环境中要小心。 + // KnownProxies = { IPAddress.Parse("YOUR_PROXY_IP_HERE") } + // KnownNetworks = { new IPNetwork(IPAddress.Parse("YOUR_PROXY_NETWORK_START_IP"), YOUR_PROXY_NETWORK_CIDR) } +}); + + +// 2. 配置 ASP.NET Core 内置异常页(开发环境) +if (app.Environment.IsDevelopment()) +{ +} +else +{ + app.UseHsts(); +} +// 1. 中间件捕获请求管道中的异常 +app.Use(async (context, next) => +{ + try + { +#if !DEBUG + if (DateTime.Now.Hour >= 23 || DateTime.Now.Hour < 7) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsync("系统维护中~"); + return; + } +#endif + // 生成唯一ID(GUID 或 Snowflake ID) + var requestId = StringObjectIdGenerator.Instance.GenerateId().ToString(); + + // 注入请求头 + context.Request.Headers["X-Request-ID"] = requestId; + + // 存储到 HttpContext.Items 供后续使用 + context.Items["X-Request-ID"] = requestId; + // 将 ID 添加到日志上下文 + using (LogContext.PushProperty("RequestID", requestId)) + { + await next(); + } + // 处理响应状态码 + if (context.Response.StatusCode == StatusCodes.Status401Unauthorized) + { + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonConvert.SerializeObject(new + { + code = 401, + message = "未授权,请登录" + })); + } + } + catch (Exception ex) + { + LogContext.PushProperty("ErrorCode", 500); + Serilog.Log.Fatal(ex, "全局捕获的未处理异常(中间件)"); + throw; // 重新抛出以触发内置错误页面 + } +}); + +// 3. 捕获非 HTTP 管道的全局异常 +AppDomain.CurrentDomain.UnhandledException += (sender, e) => +{ + var exception = e.ExceptionObject as Exception; + LogContext.PushProperty("ErrorCode", 500); + Serilog.Log.Fatal(exception, "全局捕获的非 HTTP 未处理异常"); +}; + +TaskScheduler.UnobservedTaskException += (sender, e) => +{ + LogContext.PushProperty("ErrorCode", 500); + Serilog.Log.Fatal(e.Exception, "全局捕获的未观察到的任务异常"); + e.SetObserved(); // 标记异常已处理 +}; + + +app.UseSwagger(); +app.UseKnife4UI(c => +{ + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint($"/{serviceConfig.ServiceName}/swagger.json", serviceConfig.ServiceName); +}); + +app.UseHealthChecks("/healthChecks"); +app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List { "index.html" } }); + + +app.UseAntiforgery(); +app.UseRouting(); +app.UseCors("CorsMy"); +app.UseAuthentication(); +app.UseAuthorization(); + +//app.UseMiddleware(); +// OPTIONS 请求处理 +app.MapMethods("/{**path}", new[] { "OPTIONS" }, () => Results.NoContent()).RequireCors("CorsMy"); + +try +{ + app.UseIdentityServer(); + +} +catch (Exception ex) +{ + Console.WriteLine($"IdentityServer 中间件加载失败: {ex}"); +} + +app.MapControllers(); +app.Run(); \ No newline at end of file diff --git a/BZPT.Api/Properties/launchSettings.json b/BZPT.Api/Properties/launchSettings.json new file mode 100644 index 0000000..c89791c --- /dev/null +++ b/BZPT.Api/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "profiles": { + "BZPT.API": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:19902;http://localhost:9079" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:18175", + "sslPort": 44326 + } + } +} \ No newline at end of file diff --git a/BZPT.Api/appsettings.Production.json b/BZPT.Api/appsettings.Production.json new file mode 100644 index 0000000..7ff7395 --- /dev/null +++ b/BZPT.Api/appsettings.Production.json @@ -0,0 +1,53 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Hangfire": "Warning", + "IdentityServer4": "Debug", + "IdentityServer4.EntityFramework": "Warning" + } + } + }, + "Kestrel": { // ✅ 新增 Kestrel 配置 + "Limits": { + "MaxRequestBodySize": 104857600 // 100MB(单位:字节) + } + }, + "FormOptions": { // ✅ 新增表单配置 + "MultipartBodyLengthLimit": 104857600 // 100MB(单位:字节) + }, + "ServiceConfig": { + "DataCenterID": "dc1", + "ServiceID": "api.Sys", + "ServiceName": "系统管理API", + "ServiceVersion": "1.0", + "IOCAssemblys": "BZPT.Domains.dll,BZPT.SqlSugarRepository.dll,NPlatform.dll", + }, + "DB_HOST": "LOCALHOST", + "DB_PORT": "5236", + "DB_NAME": "BZPT", + "DB_USER": "BZPT", + "DB_PASSWORD": "BZPT&123.lld", + "LOG_DB_NAME": "LOGS_DATA", + "CERT_PATH": "./Certificates/id4svr.pfx", + "CERT_PASSWORD": "ydl825913", + "ConsulServer": "http://localhost:8500", + "AllowedOrigins": "http://localhost:19901,http://192.168.0.100:40000", + "AuthorityServer": "https://192.168.1.100:19902", + "AllowedHosts": "*", + "Urls": "http://0.0.0.0:19901", + "RedisConfig": { + "RedisType": "Normal", + "Connections": [ "redismy:6379" ], + "dbNum": 10, + "Password": "redis&&!123" + } +} \ No newline at end of file diff --git a/BZPT.Api/appsettings.json b/BZPT.Api/appsettings.json new file mode 100644 index 0000000..6b20ad8 --- /dev/null +++ b/BZPT.Api/appsettings.json @@ -0,0 +1,54 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Hangfire": "Warning" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Hangfire": "Warning", + "IdentityServer4": "Debug", + "IdentityServer4.EntityFramework": "Warning" + } + } + }, + "FormOptions": { + "MultipartBodyLengthLimit": 104857600 + }, + + "Kestrel": { + "Limits": { + "MaxRequestBodySize": 104857600 + }, + "Endpoints": { + "Http": { + "Url": "http://*:19901" + }, + "Https": { + "Url": "https://*:8001" + } + } + }, + "ServiceConfig": { + "DataCenterID": "dc1", + "ServiceID": "api.Sys", + "ServiceName": "系统管理API", + "ServiceVersion": "1.0", + "IOCAssemblys": "BZPT.Domains.dll,BZPT.SqlSugarRepository.dll,NPlatform.dll", + "AttachExtension": "gif,jpg,jpeg,png,bmp,rar,zip,doc,docx,xls,xlsx,ppt,pptx,txt,flv,apk,mp4,mpg,ts,mpeg,mp3,bak,pdf", + "AttachSize": 104857600 + }, + "LOG_DB_NAME": "LOGS_DATA", + "ConsulServer": "http://localhost:8500", + "AllowedOrigins": "http://localhost:19901,http://localhost:3000,http://localhost:5173", + "AllowedHosts": "*", + "RetryCount": 10, + "AuthorityServer": "https://106.52.199.114:8001/", + "SystemHome": "https://106.52.199.114:8000/", + "Hangfire": { + "AdminPassword": "HNjt123~" + } +} \ No newline at end of file diff --git a/BZPT.Api/log4net.config b/BZPT.Api/log4net.config new file mode 100644 index 0000000..e7390e2 --- /dev/null +++ b/BZPT.Api/log4net.config @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BZPT.Api/msyh.ttc b/BZPT.Api/msyh.ttc new file mode 100644 index 0000000..37c28de Binary files /dev/null and b/BZPT.Api/msyh.ttc differ diff --git a/BZPT.Api/start.sh b/BZPT.Api/start.sh new file mode 100644 index 0000000..5ecfc06 --- /dev/null +++ b/BZPT.Api/start.sh @@ -0,0 +1,2 @@ +systemctl start meirongsite1.service +systemctl status meirongsite1.service \ No newline at end of file diff --git a/BZPT.Api/stop.sh b/BZPT.Api/stop.sh new file mode 100644 index 0000000..4256c0e --- /dev/null +++ b/BZPT.Api/stop.sh @@ -0,0 +1,14 @@ +PID=$(netstat -nlp | grep ":19901" | awk '{print $7}' | awk -F '[ / ]' '{print $1}') +if [ $? == 0 ]; then + echo "process id is:${PID}" +else + echo "process $1 no exit" + exit 0 +fi +kill -9 ${PID} +if [ $? == 0 ]; then + echo "kill $1 success " +else + echo "kill $1 fail" +fi +systemctl stop meirongsite1.service diff --git a/BZPT.Api/tempkey.jwk b/BZPT.Api/tempkey.jwk new file mode 100644 index 0000000..49e99d5 --- /dev/null +++ b/BZPT.Api/tempkey.jwk @@ -0,0 +1 @@ +{"alg":"RS256","d":"mhXFKhKsCaOocekPGwuxq85OiWDWp6K674yUWmZG3kYk9Dz9xvnUjvMVSIptu1UOrho3EFxCZQXgiHQ923Ywi78U2WRvjs2nRNe9uWL80D5TFWNKDN5WyVN_T9aK5uYkIPt7PE476-ECLTq1D4Yl0q793q1mKDCfEbfzbKfY13WYu2M3qWTrXXnC4uaYplP5TFtDvNvrQB0eYZ9qug6Cu5K9VVrsoDG5Bh8K1S-Oge2IzpSnjQGovKwRnSIdqhGyozX-jgNaCOfD6V3hzPylHaDSWvydL0xmckgpIrKh0ZfWLTJJ7nDQ1s816ku4ReXizBe6LosbozqwF9o7ocvFrQ","dp":"QAO484FPlDFcdmQc2Ab_Bdda_PCfxaP3a5PZiNYCTGGadD96BZUfkKIsW03cDDXIJCAerNedWfHM4eYkwHoBnYW5J9K1DTo2LctPIQ2dTUcTDpkXBmgfC7NCt0fz_hfG3Lp0syQwWTLCbHAJSpDHvFz_AgDDyvW4K40qITnK0dM","dq":"uuHvKDNMAblI1YiPVlV2jwBUKXJgsVdoKmRAXtdn4OHnnyfzpexp9guVNDJbSCaSMXKpO0gI6BOVxZPQcz-AIs6p3eZRnOUQoMpCSK0AK1jnXTt3RKZDYRFdze8_UhLpIi8zyU2uY9efXBRVvrCNECNNu5qaH-4zazTItwRqAHc","e":"AQAB","kid":"E48B1ABC25F3D2C6B152184A11F86432","kty":"RSA","n":"s4JFCJPMYNHHYyKH5MgEEz2Dchsq-XeJN_6Gbq0Rs0CcqhPuomUNBes49_UkrE1s_87y3V7fPHdq6UgnIn1x3z4IjGLT1PlaDA4Lyp1F-QPqQ7dZlymfrZ_YsnNkHhgRncjtp7bAu2b_E8kzo06_O8uiC2tAE535ctLSPH5oQOkkkG1qzBh8ydi9znbO5X37eNLQUgi2dX8hqD9PNH8ldAiW8G-WPgst7DarLdeiN4yYvcMAs5qjXisp6WwO2g84AkYLW2Gle3l4Sy8a3vMlQpZlFV3sdieecUxwXoN_tECNvzDVzhi2cFkZXIztiWhwBGWfMs0TiKQ13XbWpKQVaQ","p":"1KA4xDQykXTYDFv0ELWG6CUQ4jft6r6OsA35mXgylGaw6x-EwYRDmBIgEPKQHymdIg-8SRolaaqWKb1EgB7AMr9ooi33kNMqVf0HILFDJ5cRCAHWZa7gU7CGCJ-uSYK0YM6Sm1D-RDVYpPa-44WaN4OyMR5YP8AaLE_x3OPVOyc","q":"2CCbckoPaL5AOVLnY22sp6pBOyrpeFvyOIiMWClpCWr9Qz_gQn9PgT88jjnLRkS4nqOW4OA6w32nCdLX9R8pxjv8Y3mvRPicnqXwsvXjpBVjjUmPYVXq78NaPf5MTk6E2GyN8N-FyE8NTXWjbs0VI-uEalexFRWfRPpgGoN4xO8","qi":"YxmKUSoOybTEqOzSb9cfYb99wJS3SzXBHOhg4Q1tECNMHUsxe5pIoTMMhcqrUcd_qnQH5jmI0gGCnjHEkpDl7FhiuLSqKo7pgEOixYe3cMI_aMMaaiJ-BrKaMls1TFznWPp_wPV42Ym_YB1DhVKVX2vqupi55tvb7F5bT5qExFI"} \ No newline at end of file diff --git a/BZPT.Api/tips.png b/BZPT.Api/tips.png new file mode 100644 index 0000000..f533fe9 Binary files /dev/null and b/BZPT.Api/tips.png differ diff --git a/BZPT.DTO/BZPT.DTO.csproj b/BZPT.DTO/BZPT.DTO.csproj new file mode 100644 index 0000000..111cd0e --- /dev/null +++ b/BZPT.DTO/BZPT.DTO.csproj @@ -0,0 +1,40 @@ + + + net8.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BZPT.DTO/Captcha/CaptchaModel.cs b/BZPT.DTO/Captcha/CaptchaModel.cs new file mode 100644 index 0000000..e4b0f55 --- /dev/null +++ b/BZPT.DTO/Captcha/CaptchaModel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BZPT.DTO.Captcha +{ + public class CaptchaModel + { + public int X { get; set; } + public int Y { get; set; } + public Image Background { get; set; } + public Image Slide { get; set; } + } + public class Captcha64Model + { + public int X { get; set; } + public int Y { get; set; } + public string Background { get; set; } + public string Slide { get; set; } + } + +} diff --git a/BZPT.DTO/Const/ConstType.cs b/BZPT.DTO/Const/ConstType.cs new file mode 100644 index 0000000..e1250a6 --- /dev/null +++ b/BZPT.DTO/Const/ConstType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NPlatform.Const +{ + public class ConstType + { + public const string BusinessType_CZ = "充值办卡"; + public const string BusinessType_HK = "消费开单"; + + public const string OrderItemType_HK = "划卡"; + public const string OrderItemType_Item = "单项"; + public const string OrderItemType_Product = "产品"; + } +} diff --git a/BZPT.DTO/Readme.md b/BZPT.DTO/Readme.md new file mode 100644 index 0000000..bc0c24f --- /dev/null +++ b/BZPT.DTO/Readme.md @@ -0,0 +1,6 @@ +基于实际项目情况,Dto对象使用 CodeSmith脚本初始化,生成的文件名为 *.Designer.cs,减少开发了,便于落地。 + +DTO封装的是数据传输对象,有时候充当VO。 +|----VO 用于UI呈现的 ViewModel。 +|----Commands 是用于执行 CUD 操作的命令对象,他其实也算是数据传输对象的一种。 +|----Querys 是用于封装查询对象。 \ No newline at end of file diff --git a/BZPT.DTO/VO/TreeItem.cs b/BZPT.DTO/VO/TreeItem.cs new file mode 100644 index 0000000..b9eb9cf --- /dev/null +++ b/BZPT.DTO/VO/TreeItem.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BZPT.Dto.VO { + public class TreeItem: IVO { + public string Name { get; set; } + public string CreateTime { get; set; } + public string CreateUser { get; set; } + public string Status { get; set; } + public string Sort { get; set; } + public string Description { get; set; } + public bool IsLeaf { get; set; } + public string LevelCode { get; set; } + public string Id { get; set; } + public TreeItem[] Children { get; set; } + } +} diff --git a/BZPT.Domains/AutoMapperProfiles/AutoMapperProfile.cs b/BZPT.Domains/AutoMapperProfiles/AutoMapperProfile.cs new file mode 100644 index 0000000..bda7411 --- /dev/null +++ b/BZPT.Domains/AutoMapperProfiles/AutoMapperProfile.cs @@ -0,0 +1,190 @@ +//using AutoMapper; +//using NPlatform.AutoMap; +//using BZPT.Domains.Entity; +//using BZPT.Domains.Entity.Sys; +//using BZPT.Dto.VO; +//using NPlatform.Result; +//using System.Collections; +//using System.Collections.Generic; +//using System.Text; +//using BZPT.Dto.Sys; +//using System.Configuration; +//using System.Reflection; +//using BZPT.DTO.VO; +//using BZPT.Dto.Std; +//using System.Security.Cryptography; +//using BZPT.DTO.Const; +//using NPlatform.Infrastructure.Redis; +//using NPlatform.Infrastructure; +//using NPOI.SS.Formula.Functions; +//using ServiceStack.DataAnnotations; + +//namespace BZPT.Domains +//{ +// public class AutoMapperProfile : Profile, IProfile +// { +// [Autowired] +// private IRedisService _redisService { get; set; } +// private Dictionary user; +// public AutoMapperProfile(IRedisService redisService) +// { +// _redisService = redisService; +// user = this._redisService.GlobalHashGetAllAsync(CacheKeyBZPT.CACHE_USER_ALL).Result; + +// //用户到VO对象的映射 +// CreateMap() +// .ForMember(dst=>dst.Organization,src=>src.MapFrom(z=>z.Organization.Name))// 指定某个的属性从另一个对象的子属性获取(展平对象) +// .ForMember(dst => dst.RoleName,src => src.MapFrom(z => GetRoleName(z.Roles))) //来源字段 +// .ForMember(dst => dst.DutyName, src => src.MapFrom(z => z.Dutys == null ? string.Empty : GetDutyName(z.Dutys))) +// .ForMember(dst => dst.DutyID, +// src => src.MapFrom(z => z.Dutys == null || !z.Dutys.Any() +// ? new string[] { } // 返回 [] +// : GetDutyID(z.Dutys).ToArray())) // 如果有元素,连接两个空字符串并返回 +// .ForMember(dst => dst.FilePath, src => src.MapFrom(z => z.OtherAttachments.SA04)) //来源字段 +// .ForMember(dst => dst.Email, src => src.MapFrom(z => DCodeEmail(z.Email))) +// .ForMember(dst => dst.MobileNum,opt => opt.MapFrom(src => DCodedMobile(src.MobileNum))) +// .ForMember(dst => dst.CreateUserName, src => src.MapFrom(z => GetUserName(z.CreateUser))) +// .ReverseMap(); + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))) +// .ForMember(dst => dst.Password, src => ReplacePassword()) +// .ForMember(dst => dst.Email, src => src.MapFrom(z => DCodeEmail(z.Email))) +// .ForMember(dst => dst.MobileNum, opt => opt.MapFrom(src => DCodedMobile(src.MobileNum))) +// .ReverseMap(); + +// CreateMap() +// .ForMember(dst => dst.Parents, src => src.MapFrom(z => z.Parents.Split(",", StringSplitOptions.RemoveEmptyEntries))) +// .ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); +// CreateMap().ReverseMap(); + +// CreateMap().ReverseMap(); +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap().ReverseMap(); + +// CreateMap(MemberList.Destination).ReverseMap(); + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); +// CreateMap() +// .ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))) +// .ForMember(dst => dst.CreateUserId, src => src.MapFrom(z => z.CreateUser)) +// .ReverseMap(); + +// CreateMap().ReverseMap(); +// CreateMap().ReverseMap(); +// CreateMap().ReverseMap(); +// CreateMap().ReverseMap(); +// CreateMap() +// .ForMember(dst => dst.CreateUserId, src => src.MapFrom(z => z.CreateUser)) +// .ForMember(dst=>dst.CreateUser,src=>src.MapFrom(z=> GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap() +// .ForMember(dst => dst.CreateUserId, src => src.MapFrom(z => z.CreateUser)) +// .ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap() +// .ForMember(dst => dst.CreateUserId, src => src.MapFrom(z => z.CreateUser)) +// .ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))).ReverseMap(); + +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))) +// .ForMember(dst => dst.FilePath, src => src.MapFrom(z => z.OtherAttachments.SA02)) +// .ReverseMap(); +// CreateMap().ForMember(dst => dst.CreateUser, src => src.MapFrom(z => GetUserName(z.CreateUser))) +// .ForMember(dst => dst.FilePath, src => src.MapFrom(z => z.OtherAttachments.SA04)) +// .ReverseMap(); +// CreateMap().ReverseMap(); +// CreateMap().ReverseMap(); + +// } +// public string ReplacePassword() +// { +// return string.Empty; +// } +// public string GetRoleName(List roles) { +// if(roles == null || roles.Count == 0)return string.Empty; +// StringBuilder strRole = new StringBuilder(); +// foreach (Role role in roles) { +// strRole.Append(role.Name); +// strRole.Append(","); +// } +// return strRole.ToString(); +// } + +// public string GetDutyName(List Dutys) +// { +// StringBuilder strDuty = new StringBuilder(); +// foreach (Duty duty in Dutys) +// { +// strDuty.Append(duty.Name); +// strDuty.Append(","); +// } +// return strDuty.ToString(); +// } + +// public string GetUserName(string userid) +// { +// if (user.ContainsKey(userid)) +// return user[userid]; +// else return userid; +// } + +// // GetDutyID 方法 +// public string[] GetDutyID(List Dutys) +// { +// if (Dutys == null || Dutys.Count == 0) +// { +// return new string[0]; // 如果传入的列表为空,返回空字符串数组 +// } + +// string[] dutyIds = new string[Dutys.Count]; + +// for (int i = 0; i < Dutys.Count; i++) +// { +// dutyIds[i] = Dutys[i].Id; // 将每个 Duty 的 Id 存入数组 +// } + +// return dutyIds; +// } + +// public string DCodeEmail(string eml) +// { +// var deml=AES.Decode(eml); +// return MaskingHelper.MaskEmail(deml); +// } +// public string DCodedMobile(string phone) +// { +// phone = AES.Decode(phone); +// return MaskingHelper.MaskMobile(phone); +// } + +// /// +// /// 字符串转时间类型 +// /// +// public class DateTimeTypeConverter : ITypeConverter +// { +// public DateTime Convert(string source, DateTime destination, ResolutionContext context) +// { +// return System.Convert.ToDateTime(source); +// } +// } +// /// +// /// 字符串转Type类型 +// /// +// public class TypeTypeConverter : ITypeConverter +// { +// public Type Convert(string source, Type destination, ResolutionContext context) +// { +// return Assembly.GetEntryAssembly().GetType(source); +// } +// } +// } +//} diff --git a/BZPT.Domains/BZPT.Domains.csproj b/BZPT.Domains/BZPT.Domains.csproj new file mode 100644 index 0000000..4dc2c39 --- /dev/null +++ b/BZPT.Domains/BZPT.Domains.csproj @@ -0,0 +1,59 @@ + + + net8.0 + CS0619 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BZPT.Domains/Entity/Tenant.cs b/BZPT.Domains/Entity/Tenant.cs new file mode 100644 index 0000000..9f8d846 --- /dev/null +++ b/BZPT.Domains/Entity/Tenant.cs @@ -0,0 +1,101 @@ +using NPlatform.Filters; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BZPT.Domains.Entity.Sys +{ + /// + /// 租户 + /// + [Serializable] + [Table(name: "Sys_Tenant")] + public class Tenant : EntityBase + { + /// + /// 租户名称 + /// + + [Display(Name = "租户名称")] + [StringLength(200)] + [Required] + public string TenantName { get; set; } + + /// + /// 租户编码 + /// + [Display(Name = "租户编码")] + [StringLength(200)] + public string TenantCode { get; set; } + + /// + /// 数据库连接 + /// + [Display(Name = "数据库连接")] + [StringLength(600)] + [Required] + public string ConnectionString { get; set; } + + /// + /// DbType + /// + [Display(Name = "DbType")] + [StringLength(200)] + public string DbType { get; set; } + + /// + /// 缓存连接地址 + /// + [Display(Name = "缓存连接地址")] + [StringLength(600)] + public string CacheConnection { get; set; } + + + /// + /// 附件存储路径 + /// + [Display(Name = "附件存储路径")] + [StringLength(200)] + public string FilePath { get; set; } + + /// + /// 扩展字段1 + /// + [Display(Name = "扩展字段1")] + [StringLength(200)] + public string Col1 { get; set; } + + /// + /// 扩展字段2 + /// + [Display(Name = "扩展字段2")] + [StringLength(200)] + public string Col2 { get; set; } + + /// + /// 扩展字段3 + /// + [Display(Name = "扩展字段3")] + [StringLength(500)] + public string Col3 { get; set; } + + /// + /// 扩展字段4 + /// + [Display(Name = "扩展字段4")] + [StringLength(500)] + public string Col4 { get; set; } + + /// + /// 扩展字段5 + /// + [Display(Name = "扩展字段5")] + [StringLength(1000)] + public string Col5 { get; set; } + + } +} diff --git a/BZPT.Domains/IService/ICRUDService.cs b/BZPT.Domains/IService/ICRUDService.cs new file mode 100644 index 0000000..e86a094 --- /dev/null +++ b/BZPT.Domains/IService/ICRUDService.cs @@ -0,0 +1,24 @@ +using BZPT.Domains.Entity.Sys; +using BZPT.Dto.VO; +using DevExtreme.AspNet.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BZPT.Domains.IService +{ + public interface ICRUDService + { + Task Add(TDto addCommand); + Task Delete(string userId); + Task Edit(TDto editCommand); + Task> GetListAsync(QueryExp exp); + Task> GetListAsync(Expression> filter); + Task> GetPageAsync(QueryPageExp exp); + Task> GetPageAsync(DataSourceLoadOptions loadOptionsexp); + Task> GetPageAsync(DataSourceLoadOptions loadOptionsexp); + Task GetAsync(string userId); + } +} diff --git a/BZPT.Domains/Services/SugarServiceBase.cs b/BZPT.Domains/Services/SugarServiceBase.cs new file mode 100644 index 0000000..14e4a8a --- /dev/null +++ b/BZPT.Domains/Services/SugarServiceBase.cs @@ -0,0 +1,182 @@ +using BZPT.Domains.IRepositories.Sys; +using BZPT.Dto.Sys; +using BZPT.Repositories; +using Consul.Filtering; +using DevExtreme.AspNet.Data; +using NetTaste; +using NPlatform.Domains.Entity; +using NPOI.SS.Formula.Functions; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace BZPT.Domains.Services +{ + public class SugarServiceBase :BaseService where TDto : BaseDto where TEntity:EntityBase + { + #region CUD + [Autowired] + public ILogger Loger { get; set; } + [Autowired] + public IRepositorySugarBase Repository { get; set; } + + public virtual async Task Add(TDto add) + { + var vRst = add.Validates(); + if (vRst.StatusCode != HttpStatusCode.OK.ToInt()) + { + return vRst; + } + + var entity = this.MapperService.Map(add); + entity.Id = StringObjectIdGenerator.Instance.GenerateId().ToString(); + var rst = await Repository.AddsAsync(entity); + return Success(entity.Id); + } + + public virtual async Task Delete(TDto dto) + { + var vRst = dto.Validates(); + if (vRst.StatusCode != HttpStatusCode.OK.ToInt()) + { + return vRst; + } + var rstCount = 0; + if (!string.IsNullOrWhiteSpace(dto.Id)) + { + rstCount = await this.Repository.RemoveAsync(dto.Id); + } + return new SuccessResult(rstCount); + } + + public virtual async Task Delete(string id) + { + if(string.IsNullOrWhiteSpace(id)) + { + return new FailResult($"id 参数不能为空"); + } + var rstCount = await this.Repository.RemoveAsync(t=>t.Id== id); + return new SuccessResult(rstCount); + } + + + public virtual async Task Delete(Expression< Func> deleteWhere) + { + var rstCount = await this.Repository.RemoveAsync(deleteWhere); + return new SuccessResult(rstCount); + } + public virtual async Task Edit(TDto edit) + { + var vRst = edit.Validates(); + if (vRst.StatusCode != HttpStatusCode.OK.ToInt()) + { + return vRst; + } + + var entity = this.MapperService.Map(edit); + var rstCount = await this.Repository.AddOrUpdate(entity); + return new SuccessResult(rstCount); + } + #endregion + + #region Query + public override string GetDomainShortName() + { + return "请重写GetDomainShortName"; + } + + public virtual async Task> GetListAsync(QueryExp exp) + { + var vResult = exp.Validates(); + if (vResult.StatusCode == 200) + { + var srcItems = await Repository.GetListByExpAsync(exp.GetExp(), exp.GetSelectSorts()); + var dtos = this.MapperService.Map, ListResult>(srcItems); + return dtos; + } + return (IListResult)vResult; + } + + public virtual async Task> GetListAsync(Expression> filter) + { + var entitys = await Repository.GetListByExpAsync(filter); + var dtos = MapperService.Map, IListResult>(entitys); + return dtos; + } + public virtual async Task> GetPageAsync(QueryPageExp exp) + { + var vResult = exp.Validates(); + if (vResult.StatusCode == 200) + { + var entitys = await Repository.GetPagedAsync(exp.PageNum, exp.PageSize, exp.GetExp(), exp.GetSelectSorts()); + var dtos = MapperService.Map, IListResult>((IListResult)entitys); + return dtos; + } + return (IListResult)vResult; + } + + + public virtual async Task> GetPageAsync(DataSourceLoadOptions loadOptionsexp) + { + if (loadOptionsexp.Sort == null || loadOptionsexp.Sort.Length == 0) + { + loadOptionsexp.Sort = new SortingInfo[] { new SortingInfo() { Selector = "CreateTime", Desc = true } }; + } + Type type = typeof(TEntity); + // 检查是否存在指定名称的接口 + Type interfaceType = type.GetInterface("ILogicDelete"); + if (interfaceType != null && !loadOptionsexp.Filter.Contains("IsDeleted")) + { + loadOptionsexp.And(new List(){ + "IsDeleted","=",false }); + } + var entitys = await Repository.GetPagedAsync(loadOptionsexp); + var dtos = MapperService.Map, IListResult>((IListResult)entitys); + return dtos; + } + + public virtual async Task> GetPageAsync(DataSourceLoadOptions loadOptionsexp) + { + if (loadOptionsexp.Sort == null || loadOptionsexp.Sort.Length == 0) + { + loadOptionsexp.Sort = new SortingInfo[] { new SortingInfo() { Selector = "CreateTime", Desc = true } }; + } + Type type = typeof(TEntity); + // 检查是否存在指定名称的接口 + Type interfaceType = type.GetInterface("ILogicDelete"); + if (interfaceType != null&&!loadOptionsexp.Filter.Contains("IsDeleted")) + { + loadOptionsexp.And(new List(){ + "IsDeleted","=",false }); + } + var entitys = await Repository.GetPagedAsync(loadOptionsexp); + var dtos = MapperService.Map, IListResult>((IListResult)entitys); + return dtos; + } + + /// + /// GetAsync + /// + /// ID + /// + public virtual async Task GetAsync(string entityId) + { + if (string.IsNullOrEmpty(entityId)) + { + return base.FailParams(nameof(entityId)); + } + + var entity = await Repository.FindByAsync(entityId); + var entityVo = MapperService.Map(entity); + return Success(entityVo); + } + + + #endregion + } +} diff --git a/BZPT.SqlSugarRepository/BZPT.SqlSugarRepository.csproj b/BZPT.SqlSugarRepository/BZPT.SqlSugarRepository.csproj new file mode 100644 index 0000000..5049242 --- /dev/null +++ b/BZPT.SqlSugarRepository/BZPT.SqlSugarRepository.csproj @@ -0,0 +1,44 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BZPT.SqlSugarRepository/DBContext.cs b/BZPT.SqlSugarRepository/DBContext.cs new file mode 100644 index 0000000..d90567d --- /dev/null +++ b/BZPT.SqlSugarRepository/DBContext.cs @@ -0,0 +1,218 @@ +using BZPT.Domains.Application; +using BZPT.Domains.Entity.Sys; +using Castle.Core.Logging; +using Microsoft.Extensions.Logging; +using Mysqlx.Expr; +using Newtonsoft.Json; +using NPlatform.Filters; +using NPlatform.Infrastructure.IdGenerators; +using NPOI.HPSF; +using NPOI.SS.Formula.Functions; +using Serilog.Context; +using Serilog.Events; +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Configuration; +using System.Data.SqlTypes; +using System.Runtime.CompilerServices; + +namespace BZPT.Repositories +{ + public partial class DBContext : SqlSugarClient + { + private readonly string _connectionString; + private readonly DbType _dbProvider; + private readonly int _timeOut; + + public DBContext(string connectString, DbType dbProvider, string ConfigId = "default", int timeOut = 180) + : base(new ConnectionConfig() + { + DbType = dbProvider, + ConnectionString = connectString, + IsAutoCloseConnection = true, + ConfigId = ConfigId, + ConfigureExternalServices = new ConfigureExternalServices() + { + EntityService = (property, column) => + { + var attributes = property.GetCustomAttributes(true); + if (attributes.Any(it => it is KeyAttribute)) + { + column.IsPrimarykey = true; + } + if (attributes.Any(it => it is NotMappedAttribute)) + { + column.IsIgnore = true; + } + if (attributes.Any(t => t is ColumnAttribute)) + { + var attr = attributes.First(it => it is ColumnAttribute) as ColumnAttribute; + if(!string.IsNullOrEmpty(attr?.Name)) + column.DbColumnName = attr?.Name; + } + }, + EntityNameService = (type, entity) => + { + var attributes = type.GetCustomAttributes(true); + if (attributes.Any(it => it is TableAttribute)) + { + var attr = attributes.First(it => it is TableAttribute) as TableAttribute; + if(!string.IsNullOrEmpty(attr?.Name)) + entity.DbTableName = attr?.Name; + } + } + } + }, dbAction) + { + _connectionString = connectString; + _dbProvider = dbProvider; + _timeOut = timeOut; + } + + public static Action dbAction = db => + { + // SQL执行完 + db.Aop.OnLogExecuted = (sql, pars) => + { + try + { + var strSql = UtilMethods.GetNativeSql(sql, pars); + //Serilog.Log.Logger.Information($"time:{db.Ado.SqlExecutionTime.ToString()},sql:{strSql}"); + } + catch (Exception ex) + { + // 处理异常,例如记录错误日志 + Serilog.Log.Logger.Error(ex, "sql执行错误"); + MessageApplication.SendErrorMessage("sql执行错误:" + ex?.ToString()); + } + }; + + db.Aop.OnLogExecuting = (sql, pars) => + { + // 获取原生SQL推荐 5.1.4.63 性能OK + // UtilMethods.GetNativeSql(sql, pars) + + // 获取无参数化SQL 影响性能只适合调试 + // UtilMethods.GetSqlString(DbType.SqlServer, sql, pars) + }; + + db.Aop.OnError = (exp) => + { + try + { + var sql = exp.Sql; + MessageApplication.SendErrorMessage("sql执行错误:" + exp.ToString()); + if (exp.Parametres != null) + { + foreach (var p in (exp.Parametres as SugarParameter[])) + { + switch (p.DbType) + { + case System.Data.DbType.String: + case System.Data.DbType.Date: + case System.Data.DbType.DateTime: + sql = sql.Replace(p.ParameterName, $"'{p.Value}'"); + break; + default: + sql = sql.Replace(p.ParameterName, $"{p.Value}"); + break; + } + } + } + Serilog.Log.Logger.Error($"time:{exp?.ToString()}{db.Ado.SqlExecutionTime.ToString()},sql:{sql}"); + } + catch (Exception ex) + { + // 处理异常,例如记录错误日志 + Serilog.Log.Logger.Error($"time:{exp?.Message}"); + } + }; + + db.Aop.OnExecutingChangeSql = (sql, pars) => + { +#if DEBUG + try + { + var strSql = UtilMethods.GetNativeSql(sql, pars); + Serilog.Log.Logger.Debug($"time:{db.Ado.SqlExecutionTime.ToString()},sql:{strSql}"); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error($"OnExecutingChangeSql:{ex.ToString()}"); + // 处理异常,例如记录错误日志 + } +#endif + return new KeyValuePair(sql, pars); + }; + + db.Aop.OnDiffLogEvent = it => + { + // 操作前记录 包含: 字段描述 列名 值 表名 表描述 + var editBeforeData = it.BeforeData; + // 操作后记录 包含: 字段描述 列名 值 表名 表描述 + var editAfterData = it.AfterData; + var sql = it.Sql; + var parameter = it.Parameters; + var data = it.BusinessData; // 这边会显示你传进来的对象 + var time = it.Time; + var diffType = it.DiffType; // enum insert 、update and delete + Serilog.Log.Logger.Information($"diffType:{diffType},editBeforeData:{editBeforeData},editAfterData:{editAfterData},sql:{sql},params:{JsonConvert.SerializeObject(parameter)}"); + // Write logic + }; + + db.Aop.OnLogExecuted = (sql, p) => + { + // 执行时间超过1秒 + if (db.Ado.SqlExecutionTime.TotalSeconds > 2) + { + // 代码CS文件名 + var fileName = db.Ado.SqlStackTrace.FirstFileName; + // 代码行数 + var fileLine = db.Ado.SqlStackTrace.FirstLine; + // 方法名 + var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName; + // db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息 + + LogContext.PushProperty("CostTime", db.Ado.SqlExecutionTime.TotalSeconds); + Serilog.Log.Logger.Warning($"SqlExecutionTime:{sql},params:{JsonConvert.SerializeObject(p)},fileName:{fileName},fileLine:{fileLine},MethodName:{FirstMethodName}"); + } + // 相当于EF的 PrintToMiniProfiler + }; + + //db.Aop.DataExecuting = (oldValue, entityInfo) => + //{ + // /*** 列级别事件 :更新的每一列都会进事件 ***/ + // if (entityInfo.PropertyName == "UpdateTime" && entityInfo.OperationType == DataFilterType.UpdateByObject) + // { + // entityInfo.SetValue(DateTime.Now); // 修改UpdateTime字段 + + // /*当前列获取特性*/ + // // 5.1.3.23 + + // // entityInfo.IsAnyAttribute<特性>() + // // entityInfo.GetAttribute<特性>() + // } + + // /*** 行级别事件 :更新一条记录只进一次 ***/ + // if (entityInfo.EntityColumnInfo.IsPrimarykey) + // { + // // entityInfo.EntityValue 拿到单条实体对象 + // } + + // /*** 根据当前列修改另一列 ***/ + // // if(当前列逻辑==XXX) + // // var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date"); + // // if(properyDate!=null) + // // properyDate.SetValue(entityInfo.EntityValue,1); + + // // 可以写多个IF + + // /*** 删除生效 (只有行级事件) ***/ + // if (entityInfo.OperationType == DataFilterType.DeleteByObject) + // { + // // entityInfo.EntityValue 拿到所有实体对象 + // } + //}; + }; + } +} \ No newline at end of file diff --git a/BZPT.SqlSugarRepository/DefaultRepository.cs b/BZPT.SqlSugarRepository/DefaultRepository.cs new file mode 100644 index 0000000..69c80c1 --- /dev/null +++ b/BZPT.SqlSugarRepository/DefaultRepository.cs @@ -0,0 +1,59 @@ +/*********************************************************** +**项目名称: +**功能描述: 仓储 的摘要说明 +**作 者: 易栋梁 +**版 本 号: 1.0 +**创建日期: 2015/12/7 16:06:56 +**修改历史: +************************************************************/ + +using BZPT.Domains.Entity; +using NPlatform.Extends; +using NPlatform.Result; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NPlatform.Filters; +using BZPT.Repositories; +using KubeClient.Models; +using SqlSugar; +using Consul.Filtering; +using BZPT.Domains.Entity.Sys; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; +using MathNet.Numerics.Distributions; +using ServiceStack; +using NPOI.SS.Formula.Functions; +using Mysqlx.Expr; +using System.Text; +using DevExtreme.AspNet.Data; +using ServiceStack.Script; + +namespace BZPT.Repositories { + /// + /// 基础库、管理库共用库专用仓储。 + /// + /// 实体类型 + /// 主键类型 + public abstract class DefaultRepository : RepositoryBase + where TEntity : EntityBase, new() + { + [Autowired] + public new ILogger> loggerSvc { get; set; } + + + /// + /// 获基础信息库对象 + /// + public new ISqlSugarClient Db; + + public DefaultRepository(IRepositoryOptions option, DBContext db) :base(option, db) + { + Db= db; + } + } +} \ No newline at end of file diff --git a/BZPT.SqlSugarRepository/RepositoryBase.cs b/BZPT.SqlSugarRepository/RepositoryBase.cs new file mode 100644 index 0000000..7168c9f --- /dev/null +++ b/BZPT.SqlSugarRepository/RepositoryBase.cs @@ -0,0 +1,496 @@ +/*********************************************************** +**项目名称: +**功能描述: 仓储 的摘要说明 +**作 者: 易栋梁 +**版 本 号: 1.0 +**创建日期: 2015/12/7 16:06:56 +**修改历史: +************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SqlSugar; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; +using System.Text; +using NPlatform.Infrastructure.IdGenerators; +using NPlatform.Filters; +using System.DirectoryServices; +using DevExtreme.AspNet.Data; + +namespace BZPT.Repositories +{ + /// + /// 聚合仓储基类 + /// + /// 实体类型 + /// 主键类型 + public abstract class RepositoryBase : ResultHelper, IRepository, IRepositorySugarBase where TEntity : EntityBase, new() + { + [Autowired] + public ILogger> loggerSvc { get; set; } + + [Autowired] + public IHttpContextAccessor httpContextAccessor { get; set; } + + protected IRepositoryOptions Options; + + /// + /// 数据库连接承载对象,默认注入基础库,管理所有的租户连接。 + /// + protected DBContext ContextManage; + + // 构造函数 + public RepositoryBase(DBContext dbContext) + { + ContextManage = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); + } + + + public string? GetUserId() + { + return httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + } + + public string? GetUserName() + { + return httpContextAccessor.HttpContext?.User?.Identity?.Name; + } + + public ISqlSugarClient DbDefault + { + get + { + return ContextManage; + } + } + /// + /// 获取业务库对象 + /// + public ISqlSugarClient Db + { + get + { + var tenantId = httpContextAccessor.HttpContext?.User?.Claims?.Where(t => t.Type.ToUpper() =="TENANTID").FirstOrDefault()?.Value; + + var connection = ContextManage.GetConnection("default"); + if (!string.IsNullOrWhiteSpace(tenantId)) + { + var tenant = ContextManage.GetConnection("default").Queryable().Where(t => t.Id == tenantId).First(); + + var configId = tenantId;//集团ID(也可以叫租户ID) + if (!ContextManage.IsAnyConnection(configId)) + { //用非默认ConfigId进行测试 + //添加业务库只在当前上下文有效(原理:SqlSugarScope模式入门文档去看) + ContextManage.AddConnection(new ConnectionConfig() + { + ConfigId = configId, + ConnectionString = tenant.ConnectionString, + DbType = Enum.Parse(tenant.DbType, true), + IsAutoCloseConnection = true + }); + } + //原理说明 + //IsAnyConnection、AddConnection和GetConnection 都是Scope周期不同请求不会有影响 + connection = ContextManage.GetConnection(configId); + //可以给业务库result设置AOP和过滤滤器 + } + + // connection.QueryFilter.AddTableFilter(it => it.IsDeleted == false); + + + //// 应用过滤器 + //foreach (var ft in this.Options.QueryFilters) + //{ + // var exp = ft.Value.GetFilter(); + // if (exp != null) + // { + // connection.QueryFilter.AddTableFilter(exp); + // } + //} + + return connection; + } + } + + public TEntity this[string key] + { + get + { + if (EqualityComparer.Default.Equals(key, default(string))) + { + return default(TEntity); + } + + return this.Db.Queryable().Where(t => t.Id != null && t.Id.Equals(key)).First(); + } + set + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + //功能写法可以将插入和更新拆开,然后调用插入和更新独有特性 + var x = Db.Storageable(value).ToStorage(); + var rst = x.AsInsertable.ExecuteCommand();//不存在插入 + var rst2 = x.AsUpdateable.ExecuteCommand();//存在更新 + } + } + + /// + /// Initializes a new instance of the class. + /// 仓储基类 + /// + /// + /// 仓储配置 + /// + public RepositoryBase(IRepositoryOptions option, DBContext dbContext) + { + Options = option; + ContextManage = dbContext; + } + + #region 新增、修改、删除 + + /// + /// 异步新增 + /// + /// 新增对象的集合 + /// A representing the asynchronous operation. + public async virtual Task AddsAsync(params TEntity[] items) + { + if (items is null) + { + throw new ArgumentNullException(nameof(items)); + } + foreach(var item in items) + { + if(string.IsNullOrWhiteSpace(item.Id)) + item.Id= StringObjectIdGenerator.Instance.GenerateId().ToString(); + } + return await this.Db.Insertable(items).ExecuteCommandAsync(); + } + + + + + /// + /// 异步新增 + /// + /// 新增对象的集合 + /// A representing the asynchronous operation. + public async virtual Task AddOrUpdate(TEntity item) + { + if (item is null) + { + throw new ArgumentNullException(nameof(item)); + } + Db.Ado.CommandTimeOut = 30; // 设置30秒超时 + //功能写法可以将插入和更新拆开,然后调用插入和更新独有特性 + var x = Db.Storageable(item).ToStorage(); + var rst = await x.AsInsertable.ExecuteCommandAsync();//不存在插入 + var rst2 = await x.AsUpdateable.ExecuteCommandAsync();//存在更新 + + return rst + rst2; + } + + + + + + public virtual async Task RemoveAsync(Expression> filter) + { + if (filter == null) return -1; + if (typeof(TEntity).IsAssignableFrom(typeof(ILogicDelete))) + { + var rst = this.Db.Updateable() + .SetColumns(t => ((ILogicDelete)t).IsDeleted == true) + .Where(filter); + return await rst.ExecuteCommandAsync(); + } + else + return await Db.Deleteable().Where(filter).ExecuteCommandAsync(); + } + + public virtual async Task RemoveAsync(params string[] keys) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + return await this.RemoveAsync(t => keys.Contains(t.Id)); + } + + public async Task UpdateAsync(TEntity item) + { + return await Db.Updateable(item).ExecuteCommandAsync(); + } + public async Task UpdateAsync(Expression> columns, Expression> where) + { + return await Db.Updateable() + .SetColumns(columns)//类只能在表达示里面不能提取 + .Where(where) + .ExecuteCommandAsync(); + } + + public async Task UpdatesAsync(List entities, Expression> columns) + { + return await Db.Updateable(entities) + .UpdateColumns(columns) + .ExecuteCommandAsync(); + } + + + #endregion + + #region 查询 + + public async virtual Task ExistsAsync(string key) + { + if (EqualityComparer.Default.Equals(key, default(string))) + { + return false; + } + return await this.Db.Queryable().AnyAsync(t => t.Id != null && t.Id.Equals(key)); + } + + public async virtual Task ExistsAsync(Expression> filter) + { + if (filter == null) + { + return false; + } + //// 应用过滤器 + //foreach (var ft in this.Options.QueryFilters) + //{ + // var exp = ft.Value.GetFilter(); + // if (exp != null) + // { + // filter = filter.AndAlso(exp); + // } + //} + + return await this.Db.Queryable().AnyAsync(filter); + } + + public async Task> GetAllAsync(IEnumerable> sorts = null) + { + try + { + + // 获取可查询对象 + var query = ContextManage.Queryable(); + // 如果有排序条件,则应用排序 + if (sorts != null && sorts.Any()) + { + query = query.OrderBy(sorts); + } + Type type = typeof(TEntity); + // 如果实现了ILogicDelete接口且filter不包含IsDeleted条件,则添加IsDeleted=false条件 + if (typeof(ILogicDelete).IsAssignableFrom(type)) + { + query = query.Where(t => ((ILogicDelete)t).IsDeleted == false); + } + // 返回查询结果 + return await query.ToListAsync(); + } + catch (Exception ex) + { + throw new Exception("无法从数据库中检索数据.", ex); + } + } + + public async virtual Task FindByAsync(string key) + { + if (EqualityComparer.Default.Equals(key, default(string))) + { + return default(TEntity); + } + + return await this.Db.Queryable().Where(t => t.Id != null && t.Id.Equals(key)).FirstAsync(); + } + + public async virtual Task GetFirstOrDefaultAsync(Expression> filter) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + return await this.Db.Queryable().Where(filter).FirstAsync(); + + } + + public async virtual Task> GetListByExpAsync(Expression> filter, IEnumerable> sorts = null) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + Type type = typeof(TEntity); + // 如果实现了ILogicDelete接口且filter不包含IsDeleted条件,则添加IsDeleted=false条件 + if (typeof(ILogicDelete).IsAssignableFrom(type)) + { + if (!filter.ToString().Contains("IsDeleted")) + { + // 动态构建 t.IsDeleted == false 条件 + filter = filter.AndAlso(t => ((ILogicDelete)t).IsDeleted == false); + } + } + var setAll = this.Db.Queryable().Where(filter); + if (sorts != null) + setAll = setAll.OrderBy(sorts); + return await setAll.ToListAsync(); + } + + /// + /// EF 版本,EF实现指定字段查询比较困难,所以先查出所有字段 + /// + /// + /// + /// + /// + public async virtual Task> GetListWithColumnsAsync(IEnumerable columnNames, Expression> filter, IEnumerable> sorts = null) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + Type type = typeof(TEntity); + // 如果实现了ILogicDelete接口且filter不包含IsDeleted条件,则添加IsDeleted=false条件 + if (typeof(ILogicDelete).IsAssignableFrom(type)) + { + if (!filter.ToString().Contains("IsDeleted")) + { + // 动态构建 t.IsDeleted == false 条件 + filter = filter.AndAlso(t => ((ILogicDelete)t).IsDeleted == false); + } + } + var rst = await GetListByExpAsync(filter, sorts); + return rst; + } + + + public async virtual Task> GetPagedAsync(int pageIndex, int pageSize, Expression> filter, IEnumerable> sorts) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + RefAsync total = 0; + + Type type = typeof(TEntity); + // 如果实现了ILogicDelete接口且filter不包含IsDeleted条件,则添加IsDeleted=false条件 + if (typeof(ILogicDelete).IsAssignableFrom(type)) + { + if (!filter.ToString().Contains("IsDeleted")) + { + // 动态构建 t.IsDeleted == false 条件 + filter = filter.AndAlso(t => ((ILogicDelete)t).IsDeleted == false); + } + } + + var resultSet = this.Db.Queryable().Where(filter); + if (sorts != null) + resultSet = resultSet.OrderBy(sorts); + + var pageData = await resultSet.ToPageListAsync(pageIndex, pageSize, total); + return base.ListData(pageData, total); + } + + + public async virtual Task> GetPagedAsync(DataSourceLoadOptions loadOptions) + { + var query = this.Db.Queryable(); + + var datas=await query.LoadAsync(loadOptions); + return datas; + } + #endregion + + #region 统计 + public async Task CountAsync(Expression> filter) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return await this.Db.Queryable().CountAsync(filter); + } + + public async Task MaxAsync(Expression> selector, Expression> filter = null) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return await this.Db.Queryable().Where(filter).MaxAsync(selector); + } + + public async Task MinAsync(Expression> selector, Expression> filter = null) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return await this.Db.Queryable().Where(filter).MinAsync(selector); + } + + public async Task SumAsync(Expression> selector, Expression> filter = null) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return await this.Db.Queryable().Where(filter).SumAsync(selector); + } + + public async Task AVGAsync(Expression> selector, Expression> filter = null) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return await this.Db.Queryable().Where(filter).AvgAsync(selector); + } + + public Task SaveChangesAsync() + { + throw new NotImplementedException("sqlsugar 不支持"); + } + + #endregion + + } + +} \ No newline at end of file diff --git a/BZPT.SqlSugarRepository/SqlSugarExt.cs b/BZPT.SqlSugarRepository/SqlSugarExt.cs new file mode 100644 index 0000000..de3023a --- /dev/null +++ b/BZPT.SqlSugarRepository/SqlSugarExt.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity.Data; +using Microsoft.Extensions.DependencyInjection; +using BZPT.Domains.Entity; +using SqlSugar; +using System.Text; + +namespace BZPT.Repositories +{ + public static class SqlSugarExt + { + public static SqlSugar.DbType ToSugarDbType(this DBProvider dBProvider) + { + switch (dBProvider) + { + case DBProvider.MySqlClient: + return DbType.MySql; + case DBProvider.SqlClient: + return DbType.SqlServer; + case DBProvider.OracleClient: + return DbType.Oracle; + case DBProvider.PostgreSQL: + return DbType.PostgreSQL; + case DBProvider.SQLite: + return DbType.Sqlite; + default: + return DbType.Odbc; + } + } + + /// + /// 根据指定属性名称对序列进行排序 + /// + /// source中的元素的类型 + /// 一个要排序的值序列 + /// 属性名称 + /// 是否降序 + /// + public static ISugarQueryable OrderBy(this ISugarQueryable source, IEnumerable> sorts = null) where TEntity : IEntity, new() + { + if (sorts != null) + { + StringBuilder strOrder = new StringBuilder(); + foreach (var sort in sorts) + { + if(sort.FieldExp==null) + { + throw new Exception("未指定排序表达式FieldExp,SqlSugar 仓储禁用 FieldName 指定排序字段"); + } + source = source.OrderBy(sort.FieldExp, sort.IsAsc ? OrderByType.Asc : OrderByType.Desc); + } + } + return source; + } + + /// + /// sqlsugar + /// + /// + public static void RegisterSqlSugar(IServiceCollection service) + { + + } + } +} diff --git a/BZPT.sln b/BZPT.sln new file mode 100644 index 0000000..1c7262e --- /dev/null +++ b/BZPT.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32319.34 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceUnit", "ServiceUnit", "{E98E6E96-D64F-44CF-9EDA-3AA1D3DA825C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{93A61945-03CE-45E4-8530-A04D066FF795}" + ProjectSection(SolutionItems) = preProject + .dockerignore = .dockerignore + .gitignore = .gitignore + Dockerfile = Dockerfile + LICENSE = LICENSE + msyh.ttc = msyh.ttc + doc\README.md = doc\README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BZPT.Domains", "BZPT.Domains\BZPT.Domains.csproj", "{8F1EB32F-2DAC-4831-804B-A8567138AD3C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BZPT.DTO", "BZPT.DTO\BZPT.DTO.csproj", "{9E704A72-2F51-4B28-9CE6-16E4A3DC3042}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BZPT.SqlSugarRepository", "BZPT.SqlSugarRepository\BZPT.SqlSugarRepository.csproj", "{EF01CE3B-4E1C-4562-91D6-2056FB0B0A24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BZPT.Api", "BZPT.Api\BZPT.Api.csproj", "{52D85EB7-84F3-40CF-B7B8-FBED8C2921B2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F1EB32F-2DAC-4831-804B-A8567138AD3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F1EB32F-2DAC-4831-804B-A8567138AD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F1EB32F-2DAC-4831-804B-A8567138AD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F1EB32F-2DAC-4831-804B-A8567138AD3C}.Release|Any CPU.Build.0 = Release|Any CPU + {9E704A72-2F51-4B28-9CE6-16E4A3DC3042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E704A72-2F51-4B28-9CE6-16E4A3DC3042}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E704A72-2F51-4B28-9CE6-16E4A3DC3042}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E704A72-2F51-4B28-9CE6-16E4A3DC3042}.Release|Any CPU.Build.0 = Release|Any CPU + {EF01CE3B-4E1C-4562-91D6-2056FB0B0A24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF01CE3B-4E1C-4562-91D6-2056FB0B0A24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF01CE3B-4E1C-4562-91D6-2056FB0B0A24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF01CE3B-4E1C-4562-91D6-2056FB0B0A24}.Release|Any CPU.Build.0 = Release|Any CPU + {52D85EB7-84F3-40CF-B7B8-FBED8C2921B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52D85EB7-84F3-40CF-B7B8-FBED8C2921B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52D85EB7-84F3-40CF-B7B8-FBED8C2921B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52D85EB7-84F3-40CF-B7B8-FBED8C2921B2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8F1EB32F-2DAC-4831-804B-A8567138AD3C} = {E98E6E96-D64F-44CF-9EDA-3AA1D3DA825C} + {9E704A72-2F51-4B28-9CE6-16E4A3DC3042} = {E98E6E96-D64F-44CF-9EDA-3AA1D3DA825C} + {EF01CE3B-4E1C-4562-91D6-2056FB0B0A24} = {E98E6E96-D64F-44CF-9EDA-3AA1D3DA825C} + {52D85EB7-84F3-40CF-B7B8-FBED8C2921B2} = {E98E6E96-D64F-44CF-9EDA-3AA1D3DA825C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A684986A-43C9-48FE-B685-1BCD19ED349F} + EndGlobalSection +EndGlobal diff --git a/msyh.ttc b/msyh.ttc new file mode 100644 index 0000000..37c28de Binary files /dev/null and b/msyh.ttc differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e70a95d --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +增加简单的数据库加密,进行数据脱敏 +测试12 \ No newline at end of file