feat: add fnOS packaging, storage workflows and release pipeline
This commit is contained in:
@@ -20,6 +20,7 @@ using System.Collections.Concurrent;
|
||||
//using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
//using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
@@ -242,27 +243,35 @@ namespace dy.net.extension
|
||||
{
|
||||
// 提前创建连接字符串,避免每次创建ISqlSugarClient都调用(减少重复计算)
|
||||
string sqliteConn = CreateSqliteDBConn(dbpath);
|
||||
// Schema initialization is a one-time startup gate. Running CodeFirst in every scoped client
|
||||
// caused concurrent requests/workers to race schema changes and made upgrade failures non-fatal.
|
||||
using (var initializer = CreateSqlSugarClient(sqliteConn, initializeSchema: false))
|
||||
{
|
||||
initializer.DbMaintenance.CreateDatabase();
|
||||
initializer.CodeFirst.InitTables(_entityTypes);
|
||||
}
|
||||
services.AddScoped<ISqlSugarClient>(db =>
|
||||
{
|
||||
var sqlSugar = new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
ConnectionString = sqliteConn,
|
||||
InitKeyType = InitKeyType.Attribute,
|
||||
DbType = DbType.Sqlite,
|
||||
IsAutoCloseConnection = true
|
||||
}, db =>
|
||||
{
|
||||
// 移除空的Debug日志委托,避免空委托的内存占用
|
||||
db.Aop.OnError = (e) =>
|
||||
{
|
||||
Serilog.Log.Error(e, $"SqlSugar执行错误:{e.Message},SQL:{e.Sql}");
|
||||
};
|
||||
return CreateSqlSugarClient(sqliteConn, initializeSchema: false);
|
||||
});
|
||||
}
|
||||
|
||||
private static SqlSugarClient CreateSqlSugarClient(string connectionString, bool initializeSchema)
|
||||
{
|
||||
return new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
ConnectionString = connectionString,
|
||||
InitKeyType = InitKeyType.Attribute,
|
||||
DbType = DbType.Sqlite,
|
||||
IsAutoCloseConnection = true
|
||||
}, db =>
|
||||
{
|
||||
db.Aop.OnError = e => Serilog.Log.Error(e, $"SqlSugar执行错误:{e.Message},SQL:{e.Sql}");
|
||||
if (initializeSchema)
|
||||
{
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
// 核心优化:使用缓存的实体类型,避免每次都反射(减少GC和内存)
|
||||
db.CodeFirst.InitTables(_entityTypes);
|
||||
});
|
||||
return sqlSugar;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,6 +288,7 @@ namespace dy.net.extension
|
||||
services.AddScoped<DouyinCollectCustomSyncJob>();
|
||||
services.AddScoped<DouyinMixSyncJob>();
|
||||
services.AddScoped<DouyinSeriesSyncJob>();
|
||||
services.AddScoped<DouyinLiveStatusJob>();
|
||||
|
||||
// 提前创建Quartz的SQLite连接字符串,避免重复调用
|
||||
string quartzConn = CreateSqliteDBConn(dbPath);
|
||||
@@ -297,8 +307,11 @@ namespace dy.net.extension
|
||||
config.ConnectionString = quartzConn; // 使用提前创建的连接字符串
|
||||
config.TablePrefix = "QRTZ_";
|
||||
});
|
||||
s.UseProperties = false;
|
||||
s.UseBinarySerializer();
|
||||
// All Quartz job data used by this application consists of strings. Persist it as
|
||||
// properties and use the supported JSON serializer. BinaryFormatter is disabled on
|
||||
// current .NET runtimes and prevented manual triggers from being stored.
|
||||
s.UseProperties = true;
|
||||
s.UseSystemTextJsonSerializer();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,7 +330,7 @@ namespace dy.net.extension
|
||||
public static void AddHttpClients(this IServiceCollection services)
|
||||
{
|
||||
// 通用忽略SSL的Handler工厂:提取为局部方法,避免重复创建逻辑
|
||||
static HttpMessageHandler IgnoreSslHandlerFactory()
|
||||
static HttpMessageHandler IgnoreSslHandlerFactory(bool allowAutoRedirect = true)
|
||||
{
|
||||
var handler = new SocketsHttpHandler
|
||||
{
|
||||
@@ -326,6 +339,7 @@ namespace dy.net.extension
|
||||
ConnectTimeout = TimeSpan.FromSeconds(30), // 核心修复:移除无限超时,避免请求挂起泄漏
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(5), // 优化:连接池生命周期,自动释放闲置连接
|
||||
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), // 优化:闲置连接超时,减少内存占用
|
||||
AllowAutoRedirect = allowAutoRedirect,
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, __, ___, ____) => true
|
||||
@@ -340,7 +354,7 @@ namespace dy.net.extension
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(DouyinRequestParamManager.DY_USER_AGENT);
|
||||
client.BaseAddress = new Uri(DouyinRequestParamManager.DouyinHost);
|
||||
client.Timeout = TimeSpan.FromSeconds(60); // 设置请求超时,避免无限等待
|
||||
}).ConfigurePrimaryHttpMessageHandler(IgnoreSslHandlerFactory);
|
||||
}).ConfigurePrimaryHttpMessageHandler(() => IgnoreSslHandlerFactory());
|
||||
|
||||
// 抖音下载客户端
|
||||
services.AddHttpClient(DouyinRequestParamManager.DY_HTTP_CLIENT_DOWN, client =>
|
||||
@@ -348,7 +362,41 @@ namespace dy.net.extension
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(DouyinRequestParamManager.DY_USER_AGENT);
|
||||
client.DefaultRequestHeaders.Referrer = new Uri(DouyinRequestParamManager.DouyinHost);
|
||||
client.Timeout = TimeSpan.FromMinutes(5); // 下载超时设为5分钟,合理且不泄漏
|
||||
}).ConfigurePrimaryHttpMessageHandler(IgnoreSslHandlerFactory);
|
||||
}).ConfigurePrimaryHttpMessageHandler(() => IgnoreSslHandlerFactory(false));
|
||||
|
||||
// WebDAV 默认严格校验证书;仅当用户在界面明确开启自签证书兼容时使用 insecure 客户端。
|
||||
services.AddHttpClient("webdav", client =>
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMinutes(10);
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("dysync.net-webdav/1.0");
|
||||
});
|
||||
services.AddHttpClient("webdav-insecure", client =>
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMinutes(10);
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("dysync.net-webdav/1.0");
|
||||
}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||
});
|
||||
|
||||
// OpenList (AList) 原生 API 客户端
|
||||
services.AddHttpClient("openlist", client =>
|
||||
{
|
||||
// Cloud-backed OpenList drivers can legitimately need more than 30 seconds to
|
||||
// refresh a large directory. A 30 second HttpClient timeout surfaced as the
|
||||
// unhelpful "Operation canceled" and incorrectly tripped storage health.
|
||||
client.Timeout = TimeSpan.FromMinutes(2);
|
||||
client.DefaultRequestVersion = HttpVersion.Version11;
|
||||
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
|
||||
}).ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
|
||||
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
|
||||
MaxConnectionsPerServer = 4,
|
||||
ConnectTimeout = TimeSpan.FromSeconds(10),
|
||||
UseCookies = false
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -368,6 +416,7 @@ namespace dy.net.extension
|
||||
type.IsClass &&
|
||||
!type.IsAbstract &&
|
||||
!type.IsGenericTypeDefinition &&
|
||||
!typeof(IHostedService).IsAssignableFrom(type) &&
|
||||
type.Namespace != null &&
|
||||
(includeSubNamespaces
|
||||
? type.Namespace.StartsWith(@namespace, StringComparison.Ordinal)
|
||||
@@ -527,7 +576,12 @@ namespace dy.net.extension
|
||||
.Enrich.FromLogContext()
|
||||
.Filter.ByExcluding(e => e.Level == LogEventLevel.Information) // 排除Info级别的日志
|
||||
.Filter.ByExcluding(Matching.FromSource("Microsoft"))
|
||||
.Filter.ByExcluding(Matching.FromSource("Quartz"))
|
||||
// Quartz 的 Debug 日志非常密集,但 Warning/Error 必须保留,否则任务在进入
|
||||
// 业务代码前失败时,任务中心还没有记录,用户也看不到任何错误原因。
|
||||
.Filter.ByExcluding(e =>
|
||||
e.Level < LogEventLevel.Warning
|
||||
&& e.Properties.TryGetValue("SourceContext", out var source)
|
||||
&& source.ToString().Contains("Quartz", StringComparison.OrdinalIgnoreCase))
|
||||
.WriteTo.Console(new RenderedCompactJsonFormatter(), LogEventLevel.Debug)
|
||||
//.WriteTo.MySQL(connectionString: builder.Configuration.GetConnectionString("DbConnectionString"), tableName: "Logs") // 输出到数据库
|
||||
.WriteTo.Logger(configure => configure
|
||||
|
||||
Reference in New Issue
Block a user