using dy.net.job; using dy.net.service; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.OpenApi.Models; using Quartz; using Serilog; using Serilog.Events; using Serilog.Filters; using Serilog.Formatting.Compact; using SqlSugar; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; using System.IO.Compression; using System.Net.Http; using System.Reflection; namespace dy.net.extension { public static class ServiceExtension { public class SwaggerOptions { public string Title { get; set; } } private static DbType GetDBType(IConfiguration configuration) { DbType dbType = DbType.Sqlite; var dbtypeString = configuration["dbtype"].ToLower(); // 获取颜色枚举类型的所有枚举值 var dbtypes = Enum.GetValues(typeof(DbType)); foreach (DbType type in dbtypes) { if (type.ToString().ToLower() == dbtypeString) { dbType = type; break; } } return dbType; } private static string GetConnString(IConfiguration configuration, DbType dbType) { var connectionString = configuration["dbconn"]; if (dbType == DbType.Sqlite) { connectionString = CreateSqliteDBConn(); } return connectionString; } private static string CreateSqliteDBConn() { var fileFloder = Path.Combine(Environment.CurrentDirectory, "db"); if (!Directory.Exists(fileFloder)) { Directory.CreateDirectory(fileFloder); } var filePath = Path.Combine(fileFloder, "dy.sqlite"); string conn = $"DataSource={filePath}"; if (!File.Exists(filePath)) { File.Create(filePath).Close();//.Close(); } return conn; } public static void AddSqlsugar(this IServiceCollection services, IConfiguration configuration) { DbType dbtype = GetDBType(configuration); services.AddScoped(db => { var sqlSugar = new SqlSugarClient(new ConnectionConfig { ConnectionString = GetConnString(configuration, dbtype), InitKeyType = InitKeyType.Attribute, DbType = dbtype, IsAutoCloseConnection = true // close connection after each operation (recommended) }, db => { //单例参数配置,所有上下文生效 db.Aop.OnLogExecuting = (sql, pars) => { //Serilog.Log.Debug(sql);//输出sql }; db.Aop.OnError = (e) => { Serilog.Log.Error(e.Message); Serilog.Log.Error(e.Sql); }; db.DbMaintenance.CreateDatabase(); // 检查数据库是否存在,如果不存在则创建 Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集 Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型 var targetClasses = types .Where(t => t.Namespace != null && t.Namespace.StartsWith("dy.net.model")) .ToList(); db.CodeFirst.InitTables(targetClasses.ToArray()); }); return sqlSugar; }); } /// /// /// /// public static void AddQuartzService(this IServiceCollection services, IConfiguration configuration) { services.AddTransient(); services.AddTransient(); // 注册Quartz服务 services.AddQuartz(); services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true); services.AddTransient(); } public static void AddHttpClients(this IServiceCollection services) { services.AddHttpClient("douyin", client => { client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); client.DefaultRequestHeaders.Add("Referer", "https://www.douyin.com"); }); services.AddHttpClient("douyinfav", client => { client.DefaultRequestHeaders.Referrer = new Uri("https://www.douyin.com/user/self?showTab=like"); }); } /// /// 自动注入指定命名空间下的所有服务 /// /// 服务集合 /// 目标命名空间(如 "MyApp.Services") /// 服务所在的程序集(默认当前程序集) /// 是否包含子命名空间 public static IServiceCollection AddServicesFromNamespace( this IServiceCollection services, string @namespace, Assembly? assembly = null, bool includeSubNamespaces = false) { // 默认使用调用方的程序集 assembly ??= Assembly.GetCallingAssembly(); // 扫描程序集中指定命名空间的所有类 var types = assembly.GetTypes() .Where(type => type.IsClass && // 只处理类 !type.IsAbstract && // 排除抽象类 !type.IsGenericTypeDefinition && // 排除泛型类 (includeSubNamespaces ? type.Namespace?.StartsWith(@namespace, StringComparison.Ordinal) == true : type.Namespace == @namespace) // 匹配命名空间(含子命名空间或精确匹配) ); foreach (var type in types) { // 获取服务的生命周期(优先使用特性,默认 Transient) var lifetime = type.GetCustomAttribute()?.Lifetime ?? ServiceLifetime.Transient; // 查找该类实现的接口(优先注册为接口服务) var interfaces = type.GetInterfaces() .Where(i => !i.IsGenericType || !i.GetGenericTypeDefinition().Equals(typeof(IDisposable))) .ToList(); if (interfaces.Any()) { // 注册:接口 -> 实现类(如 IUserService -> UserService) foreach (var @interface in interfaces) { services.Add(new ServiceDescriptor(@interface, type, lifetime)); } } else { // 无接口时,直接注册类本身(如 UserService 注册为自身) services.Add(new ServiceDescriptor(type, type, lifetime)); } } return services; } /// /// SwaggerUi /// /// /// public static void UseCustomSwaggerUI(this IApplicationBuilder app, Action options) { SwaggerOptions option = new SwaggerOptions(); options?.Invoke(option); //启用中间件服务生成Swagger作为JSON终结点 app.UseSwagger(c => { //c.SerializeAsV2 = true; //c.RouteTemplate = "api-docs/{documentName}/swagger.json"; c.PreSerializeFilters.Add((swaggerDoc, httpReq) => { swaggerDoc.Servers = new List { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" } }; OpenApiPaths paths = new OpenApiPaths(); foreach (var path in swaggerDoc.Paths) { //if ( path.Key.StartsWith("/v1/api") )//做版本控制 paths.Add(path.Key, path.Value); } swaggerDoc.Paths = paths; }); }); //启用中间件服务对swagger-ui,指定Swagger JSON终结点 app.UseSwaggerUI(c => { //c.MaxDisplayedTags(5); //c.DisplayOperationId();//唯一标识操作 c.SwaggerEndpoint("/swagger/v1/swagger.json", option.Title); //c.SwaggerEndpoint("/swagger/v2/swagger.json", "V2 Docs"); c.RoutePrefix = "swagger";//根路由 c.EnableDeepLinking();//启用深度链接--不知道干嘛的 c.DisplayRequestDuration();//调试,显示接口响应时间 c.EnableValidator();//验证 c.DocExpansion(DocExpansion.List);//默认展开 c.DefaultModelsExpandDepth(-1);//隐藏model c.DefaultModelExpandDepth(3);//model展开层级 c.EnableFilter();//筛选--如果接口过多可以开启 c.DefaultModelRendering(ModelRendering.Model);//设置显示参数的实体或Example //c.SupportedSubmitMethods(SubmitMethod.Get , SubmitMethod.Head , SubmitMethod.Post);// //c.OAuthClientId("test-id"); //c.OAuthClientSecret("test-secret"); //c.OAuthRealm("test-realm"); //c.OAuthAppName("test-app"); //c.OAuthScopeSeparator(" "); //c.OAuthAdditionalQueryStringParams(new Dictionary { { "foo", "bar" } }); //c.OAuthUseBasicAuthenticationWithAccessCodeGrant(); }); } /// /// Swagger /// /// public static IServiceCollection AddSwagger(this IServiceCollection services, Action options = null) { if (options != null) services.AddSwaggerGen(options); else services.AddSwaggerGen(DefaultSwaggerGenOptions()); return services; } private static Action DefaultSwaggerGenOptions() { Action options = o => { o.OperationFilter(); o.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "dy.net API Swagger Document", //Description = "WebApi Swagger Document", //TermsOfService = new Uri("https://ddnsapi.online.com"), //Contact = new OpenApiContact //{ // Name = "剑之初", // Email = "xxxx@qq.com", // //Url = new Uri("https://ddns.online"), //}, //License = new OpenApiLicense //{ // Name = "许可证", // Url = new Uri("https://ddns.online"), //} }); o.OrderActionsBy((apiDesc) => $"{apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.HttpMethod}"); o.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() { Description = "请在下方输入:Bearer {Token}", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer", }); o.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer", } }, new[] { "readAccess", "writeAccess" } } }); o.DocumentFilter(); var XmlPath = $"{AppContext.BaseDirectory}{AppDomain.CurrentDomain.FriendlyName}.xml"; o.IncludeXmlComments(XmlPath); o.EnableAnnotations(); }; return options; } /// /// Serilog 日志拓展 /// public static void ConfigureLogging(this WebApplicationBuilder builder) { string dateFile = "";// DateTime.Now.ToString("yyyyMMdd"); Log.Logger = new LoggerConfiguration() //.MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Is(LogEventLevel.Debug) .Enrich.FromLogContext() .Filter.ByExcluding(e => e.Level == LogEventLevel.Information) // 排除Info级别的日志 .Filter.ByExcluding(Matching.FromSource("Microsoft")) .Filter.ByExcluding(Matching.FromSource("Quartz")) .WriteTo.Console(new RenderedCompactJsonFormatter(), LogEventLevel.Debug) //.WriteTo.MySQL(connectionString: builder.Configuration.GetConnectionString("DbConnectionString"), tableName: "Logs") // 输出到数据库 .WriteTo.Logger(configure => configure .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug) .WriteTo.File( $"logs/log-debug-{dateFile}.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")) //.WriteTo.Logger(configure => configure // .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Information) // .WriteTo.File( // $"logs/log-info-{dateFile}.txt", // rollingInterval: RollingInterval.Day, // outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")) .WriteTo.Logger(configure => configure .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Error) .WriteTo.File( $"logs/log-error-{dateFile}.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")) //.WriteTo.File( // $"logs/log-total-{dateFile}.txt", // rollingInterval: RollingInterval.Day, // outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", // restrictedToMinimumLevel: LogEventLevel.Debug) .CreateLogger(); } /// /// 响应压缩 /// /// services.AddMyResponseCompression(); 需要配合 app.UseResponseCompression(); /// /// public static void AddMyResponseCompression(this IServiceCollection services) { // 第一步: 配置gzip与br的压缩等级为最优 services.Configure(options => { options.Level = CompressionLevel.Optimal; }); services.Configure(options => { options.Level = CompressionLevel.Optimal; }); // 第二步: 添加中间件 services.AddResponseCompression(options => { options.EnableForHttps = true; // 添加br与gzip的Provider options.Providers.Add(); options.Providers.Add(); // 扩展一些类型 (MimeTypes中有一些基本的类型,可以打断点看看) options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "text/html; charset=utf-8", "application/xhtml+xml", "application/atom+xml", "image/svg+xml", "application/octet-stream" }); }); } } public class SwaggerAuthorizationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { operation.Parameters ??= new List(); _ = context.ApiDescription.ActionDescriptor.AttributeRouteInfo; //先判断是否是匿名访问, if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor) { var Authorizes = descriptor.MethodInfo.GetCustomAttributes(typeof(AuthorizeFilter), true); //非匿名的方法,链接中添加accesstoken值 if (Authorizes.Any()) { operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); //operation.Parameters.Add(new OpenApiParameter() //{ // Required = true, // Name = "Bearer", // In = ParameterLocation.Header, // Description = "You Must Request With token", // Style = ParameterStyle.DeepObject, //}); } } } } /// /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public partial class HiddenApiAttribute : Attribute { } /// /// /// public class SwaggerHiddenApiFilter : IDocumentFilter { public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { foreach (ApiDescription apiDescription in context.ApiDescriptions) { if (apiDescription.TryGetMethodInfo(out MethodInfo method)) { if (method.ReflectedType.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute)) || method.CustomAttributes.Any(t => t.AttributeType == typeof(HiddenApiAttribute))) { string key = "/" + apiDescription.RelativePath; if (key.Contains("?")) { int idx = key.IndexOf("?", StringComparison.Ordinal); key = key.Substring(0, idx); } swaggerDoc.Paths.Remove(key); } } } } } }