TANG JIANG 1 жил өмнө
parent
commit
a1818cf3e3

+ 20 - 0
src/yibin/Push.YiBin.Application/ApplicationStartupExtensions.cs

@@ -0,0 +1,20 @@
+using Mapster;
+using Microsoft.Extensions.DependencyInjection;
+using Push.YiBin.Application.Mappers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Push.YiBin.Application
+{
+    public static class ApplicationStartupExtensions
+    {
+        public static IServiceCollection AddApplication(this IServiceCollection services)
+        {
+            TypeAdapterConfig.GlobalSettings.Scan(typeof(MapperConfigs).Assembly);
+            return services;
+        }
+    }
+}

+ 1 - 1
src/yibin/Push.YiBin.Application/EventSubscriber.cs

@@ -5,7 +5,7 @@ using XF.Domain.Dependency;
 
 namespace Push.YiBin.Application;
 
-public class EventSubscriber : ICapSubscribe, ISingletonDependency
+public class EventSubscriber : ICapSubscribe, IScopeDependency
 {
     private readonly IPushDomainService _pushDomainService;
 

+ 12 - 0
src/yibin/Push.YiBin.Application/Mappers/MapperConfigs.cs

@@ -0,0 +1,12 @@
+using Mapster;
+
+namespace Push.YiBin.Application.Mappers
+{
+    public class MapperConfigs : IRegister
+    {
+        public void Register(TypeAdapterConfig config)
+        { 
+        
+        }
+    }
+}

+ 66 - 15
src/yibin/Push.YiBin.Host/Program.cs

@@ -1,23 +1,74 @@
-var builder = WebApplication.CreateBuilder(args);
+//var builder = WebApplication.CreateBuilder(args);
 
-// Add services to the container.
+//// Add services to the container.
 
-builder.Services.AddControllers();
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+//builder.Services.AddControllers();
+//// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+//builder.Services.AddEndpointsApiExplorer();
+//builder.Services.AddSwaggerGen();
 
-var app = builder.Build();
+//var app = builder.Build();
 
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
+//// Configure the HTTP request pipeline.
+//if (app.Environment.IsDevelopment())
+//{
+//    app.UseSwagger();
+//    app.UseSwaggerUI();
+//}
+
+//app.UseAuthorization();
+
+//app.MapControllers();
+
+//app.Run();
+
+
+using Push.YiBin.Host;
+using Serilog;
+
+Log.Logger = new LoggerConfiguration()
+    .WriteTo.Console()
+    .CreateBootstrapLogger();
+
+try
 {
-    app.UseSwagger();
-    app.UseSwaggerUI();
-}
+    Log.Information("data push service is Starting up");
 
-app.UseAuthorization();
+    var builder = WebApplication.CreateBuilder(args);
 
-app.MapControllers();
+    builder.Host
+        .ConfigureAppConfiguration((hostBuilderContext, configBuilder) =>
+        {
+            var path = Path.Combine(Directory.GetCurrentDirectory(), "config");
+            configBuilder.SetBasePath(path)
+#if DEBUG
+                .AddJsonFile("appsettings.shared.Development.json", true, true)
+#else
+                .AddJsonFile("appsettings.shared.json", true, true)
+#endif
+                .AddJsonFile("appsettings.json", false, true)
+                .AddJsonFile($"appsettings.{hostBuilderContext.HostingEnvironment.EnvironmentName}.json", true, true)
+                .AddEnvironmentVariables()
+                .AddCommandLine(args)
+                ;
+        })
+        .UseSerilog((ctx, lc) => lc
+            //.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}")
+            .Enrich.FromLogContext()
+            .ReadFrom.Configuration(ctx.Configuration))
+        ;
 
-app.Run();
+    builder
+        .ConfigureServices()
+        .ConfigurePipelines()
+        .Run();
+}
+catch (Exception ex)
+{
+    Log.Fatal(ex, "Unhandled exception");
+}
+finally
+{
+    Log.Information("Shut down complete");
+    Log.CloseAndFlush();
+}

+ 2 - 16
src/yibin/Push.YiBin.Host/Properties/launchSettings.json

@@ -1,13 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/launchsettings.json",
-  "iisSettings": {
-    "windowsAuthentication": false,
-    "anonymousAuthentication": true,
-    "iisExpress": {
-      "applicationUrl": "http://localhost:24513",
-      "sslPort": 0
-    }
-  },
+
   "profiles": {
     "http": {
       "commandName": "Project",
@@ -18,14 +11,7 @@
       "environmentVariables": {
         "ASPNETCORE_ENVIRONMENT": "Development"
       }
-    },
-    "IIS Express": {
-      "commandName": "IISExpress",
-      "launchBrowser": true,
-      "launchUrl": "swagger",
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      }
     }
+
   }
 }

+ 3 - 0
src/yibin/Push.YiBin.Host/Push.YiBin.Host.csproj

@@ -7,6 +7,9 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
+    <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.1.0" />
+    <PackageReference Include="Serilog.Sinks.MongoDB" Version="5.3.1" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
   </ItemGroup>
 

+ 109 - 0
src/yibin/Push.YiBin.Host/StartupExtensions.cs

@@ -0,0 +1,109 @@
+using Mapster;
+using Push.YiBin.Application;
+using Push.YiBin.Extensions;
+using Serilog;
+using XF.Domain.Dependency;
+using XF.Domain.Filters;
+using XF.EasyCaching;
+using XF.Utility.MQ;
+
+namespace Push.YiBin.Host;
+
+internal static class StartupExtensions
+{
+    const string CorsOrigins = "CorsOrigins";
+
+    internal static WebApplication ConfigureServices(this WebApplicationBuilder builder)
+    {
+        var services = builder.Services;
+        var configuration = builder.Configuration;
+
+        services.AddHttpContextAccessor();
+
+#if DEBUG
+        builder.WebHost.UseUrls("http://*:50108");
+#endif
+
+        services.AddHttpClient();
+
+        // services.Configure<ChannelConfiguration>(d => configuration.GetSection(nameof(ChannelConfiguration)).Bind(d));
+
+        // Add services to the container.
+        services
+            .BatchInjectServices()
+            .RegisterRepository()
+            .AddApplication()
+            ;
+
+        ////Authentication
+        //services.RegisterAuthentication(configuration);
+
+        services.AddControllers(options =>
+        {
+            options.Filters.Add<UnifyResponseFilter>();
+            options.Filters.Add<UserFriendlyExceptionFilter>();
+        })
+            ;
+
+        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+        services.AddEndpointsApiExplorer();
+
+        //swagger
+        services.RegisterSwagger();
+
+        /* CORS */
+        services.RegisterCors(configuration, CorsOrigins);
+
+        //mapster
+        services.RegisterMapper();
+
+        //mediatr
+        services.AddMediatR(d => { d.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly); });
+
+        //sqlsugar
+        services.AddSqlSugar(configuration);
+
+        //关闭TS时间
+        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+        AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
+
+        //cache
+        services.AddCache(d =>
+        {
+            var cacheConfig = configuration.GetSection("Cache").Get<CacheOptions>();
+            cacheConfig.Adapt(d);
+            d.Prefix = "Push";
+            d.TopicName = "push-topic";
+        });
+
+
+        //mq
+        services.AddMq(configuration);
+
+        return builder.Build();
+    }
+
+    internal static WebApplication ConfigurePipelines(this WebApplication app)
+    {
+        app.UseSerilogRequestLogging();
+
+        // Configure the HTTP request pipeline.
+        var swaggerEnable = app.Configuration.GetSection("Swagger").Get<bool>();
+        if (swaggerEnable)
+        {
+            app.UseSwagger();
+            app.UseSwaggerUI();
+        }
+
+        app.UseCors(CorsOrigins);
+
+        app.UseAuthentication();
+        app.UseAuthorization();
+
+        app.MapControllers()
+            .RequireAuthorization();
+        //app.MapSubscribeHandler();
+
+        return app;
+    }
+}

+ 123 - 0
src/yibin/Push.YiBin.Host/StartupHelper.cs

@@ -0,0 +1,123 @@
+using Mapster;
+using MapsterMapper;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi.Models;
+using System.Text;
+using XF.Domain.Entities;
+using XF.Domain.Exceptions;
+using XF.Domain.Options;
+using XF.Domain.Repository;
+namespace Push.YiBin.Host
+{
+    public static class StartupHelper
+    {
+
+        /// <summary>
+        /// Swagger
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterSwagger(this IServiceCollection services)
+        {
+            services.AddSwaggerGen(c =>
+            {
+                //添加文档
+                c.SwaggerDoc("v1", new OpenApiInfo() { Title = "Hotline Push", Version = "v1.0", Description = "城市热线api" });
+                var files = Directory.GetFiles(AppContext.BaseDirectory).Where(d => d.EndsWith(".xml"));
+                foreach (var file in files)
+                {
+                    c.IncludeXmlComments(file, true);
+                }
+
+                var scheme = new OpenApiSecurityScheme()
+                {
+                    Description = "Authorization header. \r\nExample: 'Bearer ***'",
+                    Reference = new OpenApiReference
+                    {
+                        Type = ReferenceType.SecurityScheme,
+                        Id = "Authorization"
+                    },
+                    Scheme = "oauth2",
+                    Name = "Authorization",
+                    In = ParameterLocation.Header,
+                    Type = SecuritySchemeType.ApiKey,
+                };
+                c.AddSecurityDefinition("Authorization", scheme);
+                var requirement = new OpenApiSecurityRequirement();
+                requirement[scheme] = new List<string>();
+                c.AddSecurityRequirement(requirement);
+            });
+
+            return services;
+        }
+
+        /// <summary>
+        /// Cors
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="configuration"></param>
+        /// <param name="corsOrigins"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterCors(this IServiceCollection services, ConfigurationManager configuration, string corsOrigins)
+        {
+            services.AddCors(options =>
+            {
+                options.AddPolicy(name: corsOrigins,
+                    builder =>
+                    {
+                        var origins = configuration.GetSection("Cors:Origins").Get<string[]>();
+                        builder.SetIsOriginAllowed(a =>
+                        {
+                            return origins.Any(origin => origin.StartsWith("*.", StringComparison.Ordinal)
+                                ? a.EndsWith(origin[1..], StringComparison.Ordinal)
+                                : a.Equals(origin, StringComparison.Ordinal));
+                        })
+                            .AllowAnyHeader()
+                            .AllowAnyMethod()
+                            .AllowCredentials();
+                    });
+            });
+
+            return services;
+        }
+
+        /// <summary>
+        /// Mapper
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterMapper(this IServiceCollection services)
+        {
+            var config = TypeAdapterConfig.GlobalSettings;
+            config.ForDestinationType<IDataPermission>()
+                .Ignore(d => d.CreatorId)
+                .Ignore(d => d.CreatorOrgId)
+                //.Ignore(d => d.CreatorOrgCode)
+                .Ignore(d => d.AreaId);
+            config.ForDestinationType<IWorkflow>()
+                .Ignore(d => d.ExpiredTimeConfigId);
+            config.ForDestinationType<IHasCreationTime>()
+                .Ignore(d => d.CreationTime);
+            config.ForDestinationType<IHasDeletionTime>().Ignore(d => d.DeletionTime);
+            config.ForDestinationType<ISoftDelete>().Ignore(d => d.IsDeleted);
+            config.ForDestinationType<IHasModificationTime>().Ignore(d => d.LastModificationTime);
+            config.ForDestinationType<Entity>().Ignore(d => d.Id);
+
+            services.AddSingleton(config);
+            services.AddScoped<IMapper, ServiceMapper>();
+
+            return services;
+        }
+
+
+
+        public static IServiceCollection RegisterRepository(this IServiceCollection services)
+        {
+            services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
+
+            return services;
+        }
+
+
+    }
+}

+ 0 - 15
src/yibin/Push.YiBin.Host/appsettings.Development.json

@@ -1,15 +0,0 @@
-{
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "SmsAccountInfo": {
-    "MessageServerUrl": "http://webservice.fway.com.cn:1432/FWebService.asmx/FWay_Service", //短信发送地址
-    "AccountUser": "CS12345", //短信系统账号
-    "AccountPwd": "9EE3899305A8FC97D6146CAC6B802E6F", //短信系统密码
-    "ReturnAccountUser": "fwkj", //短信回传账号
-    "ReturnAccountPwd": "fwkj12" //短信回传密码
-  }
-}

+ 0 - 15
src/yibin/Push.YiBin.Host/appsettings.json

@@ -1,15 +0,0 @@
-{
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  },
-  "SmsAccountInfo": {
-    "MessageServerUrl": "http://webservice.fway.com.cn:1432/FWebService.asmx/FWay_Service", //短信发送地址
-    "AccountUser": "CS12345", //短信系统账号
-    "AccountPwd": "9EE3899305A8FC97D6146CAC6B802E6F", //短信系统密码
-    "ReturnAccountUser": "fwkj", //短信回传账号
-    "ReturnAccountPwd": "fwkj12" //短信回传密码
-  }
-}

+ 82 - 0
src/yibin/Push.YiBin.Host/config/appsettings.Development.json

@@ -0,0 +1,82 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "SmsAccountInfo": {
+    "MessageServerUrl": "http://webservice.fway.com.cn:1432/FWebService.asmx/FWay_Service", //短信发送地址
+    "AccountUser": "CS12345", //短信系统账号
+    "AccountPwd": "9EE3899305A8FC97D6146CAC6B802E6F", //短信系统密码
+    "ReturnAccountUser": "fwkj", //短信回传账号
+    "ReturnAccountPwd": "fwkj12" //短信回传密码
+  },
+  "AllowedHosts": "*",
+  "ConnectionStrings": {
+    "Push": "PORT=5432;DATABASE=push;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Redis": "110.188.24.182:50179",
+    "MongoDB": "mongodb://192.168.100.121:27017"
+  },
+  "Cache": {
+    "Host": "110.188.24.182",
+    "Port": 50179,
+    //"Password": "fengwo22@@",
+    "Database": 4
+  },
+  "Swagger": true,
+  "Cors": {
+    "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://hotline.fw.com" ]
+  },
+  "IdentityConfiguration": {
+    "Password": {
+      "RequiredLength": 8,
+      "RequireNonAlphanumeric": true,
+      "RequireLowercase": true,
+      "RequireUppercase": true
+    },
+    "User": {
+      "RequireUniqueEmail": false
+    },
+    "SignIn": {
+      "RequireConfirmedAccount": false
+    },
+    "Lockout": {
+      "MaxFailedAccessAttempts": 5,
+      "DefaultLockoutTimeSpan": "00:10:00"
+    },
+    "Account": {
+      "DefaultPassword": "Fwkj@789"
+    },
+    "Jwt": {
+      "SecretKey": "e660d04ef1d3410798c953f5d7b8a4e1",
+      "Issuer": "hotline_server",
+      "Audience": "hotline",
+      "Scope": "hotline_api",
+      "Expired": 86400 //seceonds
+    }
+  },
+  "DatabaseConfiguration": {
+    "ApplyDbMigrations": false,
+    "ApplySeed": false
+  },
+  "MqConfiguration": {
+    "DbConnectionString": "PORT=5432;DATABASE=fwmq;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "UseDashBoard": true,
+    "RabbitMq": {
+      "UserName": "dev",
+      "Password": "123456",
+      "HostName": "110.188.24.182",
+      "VirtualHost": "fwt-master"
+    }
+  },
+  "FwClient": {
+    "ClientId": "hotline",
+    "ClientSecret": "08db29cc-0da0-4adf-850c-1b2689bd535d"
+  },
+  "ConfigCenter": {
+    "ServerAddresses": [ "http://110.188.24.28:8848" ],
+    "Namespace": "17503980-9b0d-4d3e-8e35-c842c41fb888", //debug
+    "ServiceName": "hotline"
+  }
+}

+ 82 - 0
src/yibin/Push.YiBin.Host/config/appsettings.json

@@ -0,0 +1,82 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "SmsAccountInfo": {
+    "MessageServerUrl": "http://webservice.fway.com.cn:1432/FWebService.asmx/FWay_Service", //短信发送地址
+    "AccountUser": "CS12345", //短信系统账号
+    "AccountPwd": "9EE3899305A8FC97D6146CAC6B802E6F", //短信系统密码
+    "ReturnAccountUser": "fwkj", //短信回传账号
+    "ReturnAccountPwd": "fwkj12" //短信回传密码
+  },
+  "AllowedHosts": "*",
+  "ConnectionStrings": {
+    "Push": "PORT=5432;DATABASE=push;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Redis": "110.188.24.182:50179",
+    "MongoDB": "mongodb://192.168.100.121:27017"
+  },
+  "Cache": {
+    "Host": "110.188.24.182",
+    "Port": 50179,
+    //"Password": "fengwo22@@",
+    "Database": 4
+  },
+  "Swagger": true,
+  "Cors": {
+    "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://hotline.fw.com" ]
+  },
+  "IdentityConfiguration": {
+    "Password": {
+      "RequiredLength": 8,
+      "RequireNonAlphanumeric": true,
+      "RequireLowercase": true,
+      "RequireUppercase": true
+    },
+    "User": {
+      "RequireUniqueEmail": false
+    },
+    "SignIn": {
+      "RequireConfirmedAccount": false
+    },
+    "Lockout": {
+      "MaxFailedAccessAttempts": 5,
+      "DefaultLockoutTimeSpan": "00:10:00"
+    },
+    "Account": {
+      "DefaultPassword": "Fwkj@789"
+    },
+    "Jwt": {
+      "SecretKey": "e660d04ef1d3410798c953f5d7b8a4e1",
+      "Issuer": "hotline_server",
+      "Audience": "hotline",
+      "Scope": "hotline_api",
+      "Expired": 86400 //seceonds
+    }
+  },
+  "DatabaseConfiguration": {
+    "ApplyDbMigrations": false,
+    "ApplySeed": false
+  },
+  "MqConfiguration": {
+    "DbConnectionString": "PORT=5432;DATABASE=fwmq;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "UseDashBoard": true,
+    "RabbitMq": {
+      "UserName": "dev",
+      "Password": "123456",
+      "HostName": "110.188.24.182",
+      "VirtualHost": "fwt-master"
+    }
+  },
+  "FwClient": {
+    "ClientId": "hotline",
+    "ClientSecret": "08db29cc-0da0-4adf-850c-1b2689bd535d"
+  },
+  "ConfigCenter": {
+    "ServerAddresses": [ "http://110.188.24.28:8848" ],
+    "Namespace": "17503980-9b0d-4d3e-8e35-c842c41fb888", //debug
+    "ServiceName": "hotline"
+  }
+}

+ 32 - 0
src/yibin/Push.YiBin.Host/config/appsettings.shared.Development.json

@@ -0,0 +1,32 @@
+{
+  "Serilog": {
+    "Using": [
+      "Serilog.Enrichers.Span",
+      "Serilog.Sinks.Console",
+      "Serilog.Sinks.Grafana.Loki"
+    ],
+    "MinimumLevel": {
+      "Default": "Information",
+      "Override": {
+        "Microsoft": "Warning",
+        "Microsoft.Hosting.Lifetime": "Information",
+        "Microsoft.AspNetCore.Authentication": "Debug",
+        "Microsoft.AspNetCore": "Warning",
+        "Microsoft.AspNetCore.SignalR": "Debug",
+        "Microsoft.AspNetCore.Http.Connections": "Debug",
+        "System": "Warning"
+      }
+    },
+    "WriteTo": [
+      {
+        "Name": "Console",
+        "Args": {
+          //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+          "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+        }
+      }
+    ],
+    "Enrich": [ "FromLogContext", "WithSpan" ]
+  }
+}

+ 33 - 0
src/yibin/Push.YiBin.Host/config/appsettings.shared.json

@@ -0,0 +1,33 @@
+{
+  "Serilog": {
+    "Using": [
+      "Serilog.Enrichers.Span",
+      "Serilog.Sinks.Console",
+      "Serilog.Sinks.Grafana.Loki"
+    ],
+    "MinimumLevel": {
+      "Default": "Information",
+      "Override": {
+        "Microsoft": "Warning",
+        "Microsoft.Hosting.Lifetime": "Information",
+        "Microsoft.AspNetCore.Authentication": "Debug",
+        "Microsoft.AspNetCore": "Warning",
+        "Microsoft.AspNetCore.SignalR": "Debug",
+        "Microsoft.AspNetCore.Http.Connections": "Debug",
+        "System": "Warning"
+      }
+    },
+    "WriteTo": [
+      {
+        "Name": "Console",
+        "Args": {
+          //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+          "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+        }
+      }
+    
+    ],
+    "Enrich": [ "FromLogContext", "WithSpan" ]
+  }
+}

+ 323 - 0
src/yibin/Push.YiBin/BaseRepository.cs

@@ -0,0 +1,323 @@
+using System.Linq.Dynamic.Core;
+using System.Linq.Expressions;
+using XF.Domain.Entities;
+
+namespace Push.YiBin
+{
+    public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity<string>, IHasCreationTime, IDataPermission, new()
+    {
+        protected ISugarUnitOfWork<PushDbContext> Uow { get; }
+        protected ISqlSugarClient Db { get; }
+
+        public BaseRepository(ISugarUnitOfWork<PushDbContext> uow)
+        {
+            Uow = uow;
+            Db = uow.Db;
+        }
+
+        public async Task<string> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
+        {
+            var excEntity = await Db.Insertable(entity).ExecuteReturnEntityAsync();
+            return excEntity.Id;
+        }
+
+        /// <summary>
+        /// 批量插入(应用场景:小数据量,超出1万条建议另行实现)
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task AddRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Insertable(entities).ExecuteCommandAsync(cancellationToken);
+        }
+
+        public async Task RemoveAsync(TEntity entity, bool? soft = false, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable(entity).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable(entity).ExecuteCommandAsync(cancellationToken);
+            }
+        }
+
+        public async Task RemoveAsync(string id, bool? soft = false, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable<TEntity>().In(id).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable<TEntity>().In(id).ExecuteCommandAsync(cancellationToken);
+            }
+        }
+
+        public async Task RemoveAsync(Expression<Func<TEntity, bool>> predicate, bool? soft, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable<TEntity>().Where(predicate).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable<TEntity>().Where(predicate).ExecuteCommandAsync(cancellationToken);
+            }
+        }
+
+        public async Task RemoveRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Deleteable<TEntity>(entities).ExecuteCommandAsync(cancellationToken);
+        }
+
+        public async Task RemoveRangeAsync(IEnumerable<TEntity> entities, bool? soft, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable<TEntity>(entities).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable<TEntity>(entities).ExecuteCommandAsync(cancellationToken);
+            }
+        }
+
+        public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entity)
+                .IgnoreColumns(ignoreAllNullColumns: true)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync(cancellationToken);
+        }
+
+        public async Task UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entities)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync(cancellationToken);
+        }
+
+        public async Task<TEntity?> GetAsync(string id, CancellationToken cancellationToken = default)
+        {
+            return await Db.Queryable<TEntity>().FirstAsync(d => d.Id == id, cancellationToken);
+        }
+
+        public TEntity Get(string id)
+        {
+            return Db.Queryable<TEntity>().First(d => d.Id == id);
+        }
+
+        public TEntity Get(Expression<Func<TEntity, bool>> predicate)
+        {
+            return Db.Queryable<TEntity>().First(predicate);
+        }
+
+
+        public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
+        {
+            return await Db.Queryable<TEntity>().FirstAsync(predicate, cancellationToken);
+        }
+
+        public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, bool isDesc, Expression<Func<TEntity, object>> orderBy, CancellationToken cancellationToken = default)
+        {
+            if (isDesc)
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Desc).FirstAsync(predicate, cancellationToken);
+            else
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Asc).FirstAsync(predicate, cancellationToken);
+        }
+
+
+        public async Task<List<TEntity>> QueryAsync(Expression<Func<TEntity, bool>>? predicate = null, params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate ??= d => true);
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+
+            return await query.ToListAsync();
+        }
+
+        public Task<bool> AnyAsync(CancellationToken cancellationToken = default) => Db.Queryable<TEntity>().AnyAsync();
+
+        public Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
+             Db.Queryable<TEntity>().AnyAsync(predicate, cancellationToken);
+
+        public Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
+             Db.Queryable<TEntity>().CountAsync(predicate, cancellationToken);
+
+        public ISugarQueryable<TEntity> Queryable(bool permissionVerify = false, bool includeDeleted = false)
+        {
+            if (includeDeleted)
+                Db.QueryFilter.Clear();
+
+            var query = Db.Queryable<TEntity>();
+            return query;
+        }
+
+        public IUpdateable<TEntity> Updateable() => Db.Updateable<TEntity>();
+
+        public IDeleteable<TEntity> Removeable() => Db.Deleteable<TEntity>();
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity) => Db.UpdateNav(entity);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity, UpdateNavRootOptions options) => Db.UpdateNav(entity, options);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities) => Db.UpdateNav(entities);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities, UpdateNavRootOptions options) => Db.UpdateNav(entities, options);
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(TEntity entity)
+        {
+            return Db.InsertNav(entity);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(TEntity entity, InsertNavRootOptions options)
+        {
+            return Db.InsertNav(entity, options);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(List<TEntity> entities)
+        {
+            return Db.InsertNav(entities);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(List<TEntity> entities, InsertNavRootOptions options)
+        {
+            return Db.InsertNav(entities, options);
+        }
+
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(TEntity entity) => Db.DeleteNav(entity);
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(List<TEntity> entities) => Db.DeleteNav(entities);
+
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(Expression<Func<TEntity, bool>> predicate) => Db.DeleteNav(predicate);
+
+        /// <summary>
+        /// 基础分页
+        /// </summary>
+        /// <param name="predicate"></param>
+        /// <param name="orderByCreator"></param>
+        /// <param name="pageIndex"></param>
+        /// <param name="pageSize"></param>
+        /// <param name="permissionVerify"></param>
+        /// <returns></returns>
+        public async Task<(int Total, List<TEntity> Items)> QueryPagedAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> orderByCreator,
+            int pageIndex,
+            int pageSize,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            RefAsync<int> total = 0;
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+            var items = await orderByCreator(query).ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+
+        public async Task<(int Total, List<TEntity> Items)> QueryPagedAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            int pageIndex,
+            int pageSize,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? includes = null,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? orderByCreator = null,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            RefAsync<int> total = 0;
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (includes is not null)
+                query = includes(query);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+            if (orderByCreator is not null)
+                query = orderByCreator(query);
+
+            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+        public async Task<List<TEntity>> QueryExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? includes = null,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? orderByCreator = null,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (includes is not null)
+                query = includes(query);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+
+            if (orderByCreator is not null)
+                query = orderByCreator(query);
+
+            return await query.ToListAsync();
+        }
+
+        public async Task<List<TEntity>> QueryExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes,
+            bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate);
+            query = includes(query);
+            return await query.ToListAsync();
+        }
+
+        public async Task<TEntity> GetExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes,
+            bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>();
+            query = includes(query);
+            return await query.FirstAsync(predicate);
+        }
+
+        public async Task<TEntity> GetExtAsync(string id, Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes, bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>();
+            query = includes(query);
+            return await query.FirstAsync(d => d.Id == id);
+        }
+
+        public async Task UpdateAsync(TEntity entity, bool ignoreNullColumns = true, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entity)
+                .IgnoreColumns(ignoreAllNullColumns: ignoreNullColumns)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync(cancellationToken);
+        }
+    }
+}

+ 117 - 0
src/yibin/Push.YiBin/Extensions/SqlSugarExtensions.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Push.YiBin.Extensions
+{
+    public static class SqlSugarExtensions
+    {
+        /// <summary>
+        /// List转Dictionary
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="list"></param>
+        /// <returns></returns>
+        public static List<Dictionary<string, object?>> ToDictionary<T>(this List<T> list)
+        {
+            var result = new List<Dictionary<string, object?>>();
+            if (list.Any())
+            {
+                foreach (var item in list)
+                {
+                    Dictionary<string, object?> dc = new();
+                    var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        dc.Add(property.Name, property.GetValue(item));
+                    }
+                    result.Add(dc);
+                }
+            }
+
+            return result;
+        }
+
+        public static DataTable ToDataTable<T>(this List<T> list, string tableName)
+        {
+            var dt = new DataTable();
+            dt.TableName = tableName; //设置表名
+
+            if (list.Any())
+            {
+                PropertyInfo[] properties = list[0].GetType().GetProperties();
+                foreach (PropertyInfo property in properties)
+                {
+                    if (IsIgnoreColumn(property)) continue;
+                    if (IsNavigateColumn(property)) continue;
+                    Type colType = property.PropertyType;
+                    if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                    {
+                        colType = colType.GetGenericArguments()[0];
+                    }
+                    dt.Columns.Add(property.Name, colType);
+                }
+
+                foreach (var item in list)
+                {
+                    ArrayList tempList = new();
+                    //var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        var obj = property.GetValue(item, null);
+                        tempList.Add(obj);
+                    }
+                    dt.LoadDataRow(tempList.ToArray(), true);
+                }
+
+                //for (int i = 0; i < list.Count; i++)
+                //{
+                //    ArrayList tempList = new();
+                //    foreach (PropertyInfo pi in propertys)
+                //    {
+                //        if (IsIgnoreColumn(pi))
+                //            continue;
+                //        object obj = pi.GetValue(list[i], null);
+                //        tempList.Add(obj);
+                //    }
+                //    object[] array = tempList.ToArray();
+                //    result.LoadDataRow(array, true);
+                //}
+            }
+
+            //var addRow = dt.NewRow();
+            //addRow["id"] = 0;
+            //addRow["price"] = 1;
+            //addRow["Name"] = "a";
+            //dt.Rows.Add(addRow);//添加数据
+
+            //var x = db.Storageable(dt).WhereColumns("id").ToStorage();//id作为主键
+            //x.AsInsertable.IgnoreColumns("id").ExecuteCommand();//如果是自增要添加IgnoreColumns
+            //x.AsUpdateable.ExecuteCommand();
+            return dt;
+        }
+
+        /// <summary>
+        /// 排除SqlSugar忽略的列
+        /// </summary>
+        /// <param name="pi"></param>
+        /// <returns></returns>
+        private static bool IsIgnoreColumn(PropertyInfo property)
+        {
+            var sc = property.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore);
+            return sc != null;
+        }
+
+        private static bool IsNavigateColumn(PropertyInfo property) =>
+            property.GetCustomAttributes<Navigate>(false).Any();
+    }
+}

+ 27 - 0
src/yibin/Push.YiBin/Extensions/SqlSugarRepositoryExtensions.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Push.YiBin.Extensions
+{
+    public static class SqlSugarRepositoryExtensions
+    {
+        public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
+            where TEntity : class, new()
+        {
+            RefAsync<int> total = 0;
+            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+        //public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, PagedRequest dto, CancellationToken cancellationToken = default)
+        //    where TEntity : class, new()
+        //{
+        //    RefAsync<int> total = 0;
+        //    var items = await query.ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+        //    return (total.Value, items);
+        //}
+    }
+}

+ 271 - 0
src/yibin/Push.YiBin/Extensions/SqlSugarStartupExtensions.cs

@@ -0,0 +1,271 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq.Dynamic.Core;
+using System.Linq.Expressions;
+using System.Reflection;
+using XF.Domain.Entities;
+using XF.Domain.Extensions;
+using XF.Domain.Options;
+using XF.Utility.SequentialId;
+
+namespace Push.YiBin.Extensions
+{
+    public static class SqlSugarStartupExtensions
+    {
+        public static void AddSqlSugar(this IServiceCollection services, IConfiguration configuration, string dbName = "Push")
+        {
+            //多租户 new SqlSugarScope(List<ConnectionConfig>,db=>{});
+
+            SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
+            {
+                DbType = DbType.PostgreSQL,
+                ConnectionString = configuration.GetConnectionString(dbName),
+                IsAutoCloseConnection = true,
+                ConfigureExternalServices = new ConfigureExternalServices
+                {
+                    EntityService = (property, column) =>
+                    {
+                        var attributes = property.GetCustomAttributes(true); //get all attributes 
+
+                        if (column.PropertyName.ToLower() == "id" ||
+                            attributes.Any(it => it is KeyAttribute)) //是id的设为主键
+                        {
+                            column.IsPrimarykey = true;
+                            column.Length = 36;
+                        }
+
+
+                        //统一设置 nullable等于isnullable=true
+                        if (!column.IsPrimarykey && new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
+                        {
+                            column.IsNullable = true;
+                        }
+                    },
+                    EntityNameService = (type, entity) =>
+                    {
+                        var attributes = type.GetCustomAttributes(true);
+                   
+
+                        entity.DbTableName = entity.DbTableName.ToSnakeCase();
+
+                        if (attributes.Any(d => d is DescriptionAttribute))
+                        {
+                            entity.TableDescription =
+                                (attributes.First(d => d is DescriptionAttribute) as DescriptionAttribute)
+                                .Description;
+                        }
+                    }
+                },
+                MoreSettings = new ConnMoreSettings
+                {
+                    PgSqlIsAutoToLower = false,//增删查改支持驼峰表
+                    PgSqlIsAutoToLowerCodeFirst = false, // 建表建驼峰表。5.1.3.30 
+                }
+            },
+                SetDbAop
+            );
+
+            ISugarUnitOfWork<PushDbContext> context = new SugarUnitOfWork<PushDbContext>(sqlSugar);
+            services.AddSingleton(context);
+
+            InitDatabase(context, configuration);
+        }
+
+        private static void InitDatabase(ISugarUnitOfWork<PushDbContext> context, IConfiguration configuration)
+        {
+            var dbOptions = configuration.GetSection("DatabaseConfiguration").Get<DatabaseOptions>() ?? new DatabaseOptions();
+            if (dbOptions.ApplyDbMigrations)
+            {
+                context.Db.DbMaintenance.CreateDatabase();
+
+                var types = typeof(Message).Assembly.GetTypes()
+                    .Where(d => d.GetInterfaces().Any(x => x == typeof(ITable) && !d.IsAbstract))
+                    .Distinct()
+                    .ToArray();
+
+                context.Db.CodeFirst.InitTables(types);//根据types创建表
+            }
+
+            if (dbOptions.ApplySeed)
+            {
+                var allTypes = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes());
+
+                var seedDataTypes = allTypes.Where(d => !d.IsInterface && !d.IsAbstract && d.IsClass
+                                                        && d.HasImplementedOf(typeof(ISeedData<>)));
+
+                foreach (var seedType in seedDataTypes)
+                {
+                    var instance = Activator.CreateInstance(seedType);
+
+                    var hasDataMethod = seedType.GetMethod("HasData");
+                    var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
+                    if (seedData == null) continue;
+
+                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
+
+                    var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
+                    if (entityInfo.Columns.Any(d => d.IsPrimarykey))
+                    {
+                        var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
+                        storage.AsInsertable.ExecuteCommand();
+                    }
+                    else
+                    {
+                        // 无主键则只进行插入
+                        if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
+                            context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
+                    }
+                }
+
+            }
+        }
+
+        #region private
+
+        private static void SetDbAop(SqlSugarClient db)
+        {
+            /***写AOP等方法***/
+            db.Aop.OnLogExecuting = (sql, pars) =>
+            {
+                //Log.Information("Sql: {0}", sql);
+                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
+            };
+            db.Aop.OnError = (exp) =>//SQL报错
+            {
+                //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
+                Log.Error("SqlError: {0}", exp.Sql);
+
+                //5.0.8.2 获取无参数化 SQL  对性能有影响,特别大的SQL参数多的,调试使用
+                //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
+            };
+            //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
+            //{
+            //    //sql=newsql
+            //    //foreach (var p in pars) //修改
+            //    //{
+
+            //    //}
+
+            //    return new KeyValuePair<string, SugarParameter[]>(sql, pars);
+            //};
+
+            db.Aop.OnLogExecuted = (sql, p) =>
+            {
+                //执行时间超过1秒
+                if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
+                {
+                    //代码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 获取上层方法的信息
+
+                    Log.Warning("slow query ==> fileName: {fileName}, fileLine: {fileLine}, FirstMethodName: {FirstMethodName}",
+                        fileName, fileLine, FirstMethodName);
+                    Log.Warning(UtilMethods.GetNativeSql(sql, p));
+                    Log.Warning("slow query totalSeconds: {sec}", db.Ado.SqlExecutionTime.TotalSeconds);
+                }
+                //相当于EF的 PrintToMiniProfiler
+            };
+
+            db.Aop.DataExecuting = (oldValue, entityInfo) =>
+            {
+                //inset生效
+                if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
+                {
+                    if (oldValue is DateTime createTime)
+                    {
+                        if (createTime == DateTime.MinValue)
+                        {
+                            entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
+                                                              //entityInfo有字段所有参数
+                        }
+                    }
+                }
+                //update生效        
+                else if (entityInfo.PropertyName == "LastModificationTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
+                {
+                    entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
+                }
+
+                //根据当前列修改另一列 可以么写
+                //if(当前列逻辑==XXX)
+                //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
+                //if(properyDate!=null)
+                //properyDate.SetValue(entityInfo.EntityValue,1);
+
+                else if (entityInfo.EntityColumnInfo.IsPrimarykey
+                         && entityInfo.EntityColumnInfo.PropertyName.ToLower() == "id"
+                         && entityInfo.OperationType == DataFilterType.InsertByObject
+                         && oldValue is null) //通过主键保证只进一次事件
+                {
+                    //var propertyId = entityInfo.EntityValue.GetType().GetProperty("Id");
+                    //if (propertyId is not null)
+                    //{
+                    //    var idValue = propertyId.GetValue(entityInfo.EntityValue);
+                    //    if (idValue is null)
+                    //        //这样每条记录就只执行一次 
+                    //        entityInfo.SetValue(SequentialStringGenerator.Create());
+                    //}
+                    entityInfo.SetValue(SequentialStringGenerator.Create());
+
+                }
+            };
+
+            SetDeletedEntityFilter(db);
+        }
+
+        private static void SetDeletedEntityFilter(SqlSugarClient db)
+        {
+            var cacheKey = $"DbFilter:{db.CurrentConnectionConfig.ConfigId}:IsDeleted";
+            var tableFilterItemList = db.DataCache.Get<List<TableFilterItem<object>>>(cacheKey);
+            if (tableFilterItemList == null)
+            {
+                // 获取基类实体数据表
+                var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes())
+                    .Where(d => !d.IsInterface
+                                && !d.IsAbstract
+                                && d.IsClass
+                                && d.GetInterfaces().Any(x => x == typeof(ISoftDelete)));
+                if (!entityTypes.Any()) return;
+
+                var tableFilterItems = new List<TableFilterItem<object>>();
+                foreach (var entityType in entityTypes)
+                {
+                    if (entityType.GetProperty("IsDeleted") is null) continue;
+                    //// 排除非当前数据库实体
+                    //var tAtt = entityType.GetCustomAttribute<TenantAttribute>();
+                    //if ((tAtt != null && (string)db.CurrentConnectionConfig.ConfigId != tAtt.configId.ToString()) ||
+                    //    (tAtt == null && (string)db.CurrentConnectionConfig.ConfigId != SqlSugarConst.ConfigId))
+                    //    continue;
+
+                    var lambda = DynamicExpressionParser.ParseLambda(new[] {
+                        Expression.Parameter(entityType, "d") },
+                    typeof(bool),
+                    $"{nameof(SoftDeleteEntity.IsDeleted)} == @0", false);
+                    var tableFilterItem = new TableFilterItem<object>(entityType, lambda);
+                    tableFilterItems.Add(tableFilterItem);
+                    db.QueryFilter.Add(tableFilterItem);
+                }
+                db.DataCache.Add(cacheKey, tableFilterItems);
+            }
+            else
+            {
+                tableFilterItemList.ForEach(u =>
+                {
+                    db.QueryFilter.Add(u);
+                });
+            }
+        }
+
+        #endregion
+    }
+}

+ 2 - 1
src/yibin/Push.YiBin/IPushDomainService.cs

@@ -1,4 +1,5 @@
-using Push.Share.Dtos;
+using DotNetCore.CAP;
+using Push.Share.Dtos;
 using Push.Share.Dtos.FWMessage;
 
 namespace Push.YiBin

+ 2 - 0
src/yibin/Push.YiBin/Push.YiBin.csproj

@@ -9,7 +9,9 @@
     <ItemGroup>
         <PackageReference Include="Mapster" Version="7.3.0" />
         <PackageReference Include="MediatR" Version="12.0.1" />
+        <PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
       <PackageReference Include="XF.Domain.Repository" Version="1.0.5" />
+      <PackageReference Include="XF.EasyCaching" Version="1.0.4" />
     </ItemGroup>
 
     <ItemGroup>

+ 12 - 0
src/yibin/Push.YiBin/PushDbContext.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Push.YiBin
+{
+    public class PushDbContext : SugarUnitOfWork
+    {
+    }
+}

+ 7 - 8
src/yibin/Push.YiBin/PushDomainService.cs

@@ -1,15 +1,12 @@
-using System.Net;
-using System.Text.RegularExpressions;
-using System.Xml;
-using DotNetCore.CAP;
+using DotNetCore.CAP;
 using MapsterMapper;
 using MediatR;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Configuration;
-using Npgsql.Replication.PgOutput.Messages;
 using Push.Share.Dtos;
 using Push.Share.Dtos.FWMessage;
 using Push.Share.Enums;
+using System.Net;
+using System.Xml;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -44,7 +41,9 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     /// <param name="capPublisher"></param>
     public PushDomainService(IRepository<Message> messageRepository, IHttpClientFactory httpClientFactory
         , IMapper mapper, IConfiguration config, IMediator mediator,
-        ITypedCache<CacheWaitSendId> cacheWaitSendId, ICapPublisher capPublisher)
+        ITypedCache<CacheWaitSendId> cacheWaitSendId,
+        ICapPublisher capPublisher
+        )
     {
         _messageRepository = messageRepository;
         _httpClientFactory = httpClientFactory;
@@ -191,7 +190,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
             data.SmsSendingCompletedId = receiveMessageDto.sfid + "";
             data.Reason = receiveMessageDto.errormsg;
             await _messageRepository.UpdateAsync(data);
-           
+
             if (data.ClientId == "Hotline")
             {
                 var dataPush = _mapper.Map<PushMessageDto>(data);