642 lines
27 KiB
C#
642 lines
27 KiB
C#
using AgileConfig.Client;
|
||
|
||
using Autofac;
|
||
using Autofac.Extensions.DependencyInjection;
|
||
|
||
using FreeSql;
|
||
|
||
using Hangfire;
|
||
using Hangfire.SqlServer;
|
||
|
||
using LiveForum.Code.Extend;
|
||
using LiveForum.Code.JsonConverterExtend;
|
||
using LiveForum.Code.JwtInfrastructure;
|
||
using LiveForum.Code.MiddlewareExtend;
|
||
using LiveForum.Code.Redis.Contract;
|
||
using LiveForum.Code.SensitiveWord.Interfaces;
|
||
using LiveForum.Model;
|
||
using LiveForum.Model.Dto.Others;
|
||
|
||
using Microsoft.Extensions.Configuration;
|
||
|
||
using Microsoft.Extensions.Options;
|
||
using Microsoft.OpenApi.Models;
|
||
|
||
using Serilog;
|
||
|
||
using SKIT.FlurlHttpClient.Wechat.Api;
|
||
|
||
using StackExchange.Redis;
|
||
|
||
using System.Reflection;
|
||
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
|
||
#region AgileConfig配置中心
|
||
// 配置AgileConfig,从appsettings.json读取AgileConfig节点配置
|
||
// 注意:AgileConfig会作为配置源添加到Configuration中,优先级高于appsettings.json
|
||
builder.Host.UseAgileConfig(e =>
|
||
{
|
||
// 从appsettings.json读取AgileConfig配置
|
||
var agileConfig = builder.Configuration.GetSection("AgileConfig");
|
||
e.AppId = agileConfig["appId"] ?? "LiveForum";
|
||
e.Secret = agileConfig["secret"] ?? "";
|
||
e.Nodes = agileConfig["nodes"] ?? agileConfig["url"] ?? "";
|
||
e.Name = agileConfig["appId"] ?? "LiveForum";
|
||
e.ENV = agileConfig["env"] ?? "DEV";
|
||
e.HttpTimeout = 100; // HTTP超时时间(秒)
|
||
e.Tag = agileConfig["env"] ?? "DEV"; // 标签,用于区分环境
|
||
});
|
||
#endregion
|
||
#region 日志
|
||
// Add services to the container.
|
||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||
.ReadFrom.Configuration(context.Configuration)
|
||
.ReadFrom.Services(services)
|
||
.Enrich.FromLogContext());
|
||
|
||
builder.Services.AddSingleton(typeof(ILogger<ExceptionMiddleware>), serviceProvider =>
|
||
{
|
||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||
return loggerFactory.CreateLogger<ExceptionMiddleware>();
|
||
});
|
||
#endregion
|
||
#region autofac
|
||
// Add services to the container.
|
||
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
|
||
// 统一在 Autofac 中注册数据访问相关对象
|
||
builder.Host.ConfigureContainer<ContainerBuilder>(autofac =>
|
||
{
|
||
var connStr = builder.Configuration.GetConnectionString("LiveForumConnection")
|
||
?? "Data Source=app.db;Cache=Shared";
|
||
|
||
var fsql = new FreeSqlBuilder()
|
||
.UseConnectionString(DataType.SqlServer, connStr)
|
||
#if DEBUG
|
||
.UseAutoSyncStructure(true)
|
||
.UseMonitorCommand(cmd => Console.WriteLine($"SQL: {cmd.CommandText}"))
|
||
#endif
|
||
.Build();
|
||
|
||
// 注册单一实例
|
||
autofac.RegisterInstance(fsql).As<IFreeSql>().SingleInstance();
|
||
|
||
//// 注册 FreeSql 仓储 - Autofac 版本
|
||
//autofac.RegisterGeneric(typeof(FreeSql.BaseRepository<>))
|
||
// .As(typeof(FreeSql.IBaseRepository<>))
|
||
// .InstancePerLifetimeScope();
|
||
|
||
// 批量注册服务层,并启用属性注入
|
||
var serviceAssembly = Assembly.Load("LiveForum.Service"); // 替换为你的服务实现所在程序集
|
||
autofac.RegisterAssemblyTypes(serviceAssembly)
|
||
.Where(t => t.IsClass && !t.IsAbstract) // 只注册具体类
|
||
.Where(t => t.Name != "SensitiveWordService") // 排除敏感词服务,使用 Program.cs 中的 Singleton 注册
|
||
.Where(t => t.Name != "WechatApiClientManager") // 排除微信客户端管理器,使用 Program.cs 中的 Singleton 注册
|
||
.AsImplementedInterfaces() // 自动关联该类实现的所有接口
|
||
.PropertiesAutowired() //启动属性注入
|
||
.InstancePerLifetimeScope(); // Web 请求作用域内单例
|
||
|
||
// Redis 相关注册
|
||
var redisConnectionString = builder.Configuration["Redis:Configuration"]
|
||
?? "localhost:6379"; // 默认值,可从配置读取,比如 appsettings.json 中的 "Redis": "localhost:6379"
|
||
|
||
// 注册 ConnectionMultiplexer(单例)
|
||
autofac.Register(c =>
|
||
{
|
||
// 创建并返回单例的 ConnectionMultiplexer
|
||
return ConnectionMultiplexer.Connect(redisConnectionString);
|
||
})
|
||
.As<ConnectionMultiplexer>()
|
||
.SingleInstance(); // 关键:注册为单例!
|
||
|
||
// 注册 IDatabase(每个请求一个实例)
|
||
autofac.Register(c =>
|
||
{
|
||
var redis = c.Resolve<ConnectionMultiplexer>();
|
||
return redis.GetDatabase(); // 默认 db0
|
||
})
|
||
.As<IDatabase>()
|
||
.InstancePerLifetimeScope();
|
||
|
||
// 注册 RedisService(统一注册一次,避免重复)
|
||
autofac.RegisterType<LiveForum.Code.RedisService>()
|
||
.As<IRedisService>()
|
||
.InstancePerLifetimeScope();
|
||
|
||
// 注册事件总线
|
||
autofac.RegisterType<LiveForum.Service.Messages.RedisMessageEventBus>()
|
||
.As<LiveForum.IService.Messages.IMessageEventBus>()
|
||
.InstancePerLifetimeScope();
|
||
|
||
// 注册消息事件处理器
|
||
autofac.RegisterType<LiveForum.Service.Messages.MessageEventHandler>()
|
||
.As<LiveForum.IService.Messages.IMessageEventHandler>()
|
||
.InstancePerLifetimeScope();
|
||
|
||
// 注册消息发布器
|
||
autofac.RegisterType<LiveForum.Service.Messages.MessagePublisher>()
|
||
.As<LiveForum.IService.Messages.IMessagePublisher>()
|
||
.InstancePerLifetimeScope();
|
||
|
||
// 注册权限服务
|
||
autofac.RegisterType<LiveForum.Service.Permission.PermissionService>()
|
||
.As<LiveForum.IService.Permission.IPermissionService>()
|
||
.InstancePerLifetimeScope();
|
||
});
|
||
#endregion
|
||
// 先看入门文档注入 IFreeSql 注入freesql 仓储类
|
||
builder.Services.AddFreeRepository();
|
||
|
||
#region Hangfire配置
|
||
// 配置Hangfire使用SQL Server存储
|
||
var hangfireConnectionString = builder.Configuration.GetConnectionString("LiveForumConnection")
|
||
?? "Data Source=app.db;Cache=Shared";
|
||
|
||
builder.Services.AddHangfire(config =>
|
||
{
|
||
config.UseSqlServerStorage(hangfireConnectionString, new SqlServerStorageOptions
|
||
{
|
||
SchemaName = "HangFire", // 表架构名称
|
||
QueuePollInterval = TimeSpan.FromSeconds(15), // 队列轮询间隔
|
||
JobExpirationCheckInterval = TimeSpan.FromHours(1), // 任务过期检查间隔
|
||
CountersAggregateInterval = TimeSpan.FromMinutes(5), // 计数器聚合间隔
|
||
PrepareSchemaIfNecessary = true, // 自动创建表结构
|
||
DashboardJobListLimit = 50000, // Dashboard显示的任务数量限制
|
||
TransactionTimeout = TimeSpan.FromMinutes(1), // 事务超时时间
|
||
CommandTimeout = TimeSpan.FromMinutes(60) // 数据库命令超时时间(支持长时间运行的任务,设置为60分钟)
|
||
});
|
||
|
||
// 使用默认的序列化设置
|
||
config.UseSimpleAssemblyNameTypeSerializer();
|
||
config.UseRecommendedSerializerSettings();
|
||
});
|
||
|
||
// 添加Hangfire服务器(执行任务)
|
||
builder.Services.AddHangfireServer(options =>
|
||
{
|
||
options.WorkerCount = 1; // 工作线程数
|
||
options.ServerName = "LiveForum-Server"; // 服务器名称
|
||
options.Queues = new[] { "default" }; // 队列名称
|
||
options.ServerTimeout = TimeSpan.FromMinutes(60); // 服务器超时时间(支持长时间运行的任务,设置为60分钟)
|
||
options.HeartbeatInterval = TimeSpan.FromSeconds(30); // 心跳间隔(服务器每30秒发送一次心跳,表示任务仍在运行)
|
||
options.SchedulePollingInterval = TimeSpan.FromSeconds(15); // 调度轮询间隔
|
||
});
|
||
#endregion
|
||
|
||
#region 注入jwt
|
||
builder.AddJwtConfig((token, tokenMd5, serviceProvider) =>
|
||
{
|
||
//在数据库中验证token有效性
|
||
var userToken = serviceProvider.GetRequiredService<IBaseRepository<T_UserTokens>>();
|
||
var userAccessToken = userToken.Select.NoTracking().Where(it => it.AccessToken == token).First(it => it.AccessToken);
|
||
if (userAccessToken == null || string.IsNullOrEmpty(userAccessToken))
|
||
{
|
||
return null;
|
||
}
|
||
return userAccessToken ?? "";
|
||
|
||
});
|
||
#endregion
|
||
|
||
builder.Services.AddControllers();
|
||
builder.Services.AddMemoryCache();
|
||
builder.Services.AddHttpClient();
|
||
builder.Services.AddHttpContextAccessor(); //添加httpContext注入访问
|
||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||
builder.Services.AddEndpointsApiExplorer();
|
||
#region swagger
|
||
builder.Services.AddSwaggerGen(c =>
|
||
{
|
||
|
||
var securityScheme = new OpenApiSecurityScheme
|
||
{
|
||
Name = "JWT 身份验证(Authentication)",
|
||
Description = "请输入登录后获取JWT的**token**",
|
||
In = ParameterLocation.Header,
|
||
Type = SecuritySchemeType.Http,
|
||
Scheme = "bearer", //必须小写
|
||
BearerFormat = "JWT",
|
||
Reference = new OpenApiReference
|
||
{
|
||
Id = "Bearer",//JwtBearerDefaults.AuthenticationScheme
|
||
Type = ReferenceType.SecurityScheme
|
||
}
|
||
};
|
||
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
|
||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||
{
|
||
{securityScheme, Array.Empty<string>()}
|
||
});
|
||
string description = "";
|
||
//.versionDescribe
|
||
var filePath = Path.GetFullPath(".versionDescribe");
|
||
if (File.Exists(filePath))
|
||
{
|
||
description = File.ReadAllText(filePath);
|
||
//string[] lines = File.ReadAllLines(filePath);
|
||
//foreach (string line in lines)
|
||
//{
|
||
// if (line.Contains("##"))
|
||
// {
|
||
// description += $"**{line}**"; // 使用Markdown的加粗
|
||
|
||
// }
|
||
// else
|
||
// {
|
||
// description += line + "<br />";
|
||
// }
|
||
//}
|
||
//description = $"{description}";
|
||
}
|
||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "论坛社区", Version = "0.1.7", Description = description });
|
||
foreach (var assemblies in AppDomain.CurrentDomain.GetAssemblies())
|
||
{
|
||
// 添加 XML 注释文件路径
|
||
var xmlFile = $"{assemblies.GetName().Name}.xml";
|
||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||
if (File.Exists(xmlPath))
|
||
{
|
||
c.IncludeXmlComments(xmlPath);
|
||
}
|
||
}
|
||
//c.ParameterFilter<LowercaseParameterFilter>();
|
||
//c.RequestBodyFilter<LowercaseRequestFilter>();
|
||
});
|
||
#endregion
|
||
|
||
builder.Services.AddControllers()
|
||
.AddNewtonsoftJson(options =>
|
||
{
|
||
// 配置 Newtonsoft.Json 选项
|
||
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; // 忽略循环引用
|
||
options.SerializerSettings.ContractResolver = new CustomContractResolver();
|
||
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();// 首字母小写(驼峰样式)
|
||
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";// 时间格式化
|
||
options.SerializerSettings.Converters.Add(new NullToEmptyStringConverter());
|
||
#if !DEBUG
|
||
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
|
||
#endif
|
||
//options.SerializerSettings.Converters.Add()
|
||
// 其他配置...
|
||
});
|
||
#region 微信小程序配置
|
||
// 绑定微信小程序配置(使用 Configure 支持热更新)
|
||
builder.Services.Configure<WechatConfig>(builder.Configuration.GetSection("Wechat"));
|
||
|
||
// 注册微信客户端管理器(单例,负责管理 WechatApiClient 的生命周期)
|
||
builder.Services.AddSingleton<LiveForum.IService.Others.IWechatApiClientManager, LiveForum.Service.Others.WechatApiClientManager>();
|
||
|
||
// 注册微信小程序服务(Scoped,使用接口,方便替换实现)
|
||
builder.Services.AddScoped<LiveForum.IService.Others.IWechatMiniProgramService, LiveForum.Service.Others.WechatMiniProgramService>();
|
||
|
||
var wechatConfig = builder.Configuration.GetSection("Wechat").Get<WechatConfig>() ?? new WechatConfig();
|
||
var appIdDisplay = string.IsNullOrEmpty(wechatConfig.AppId)
|
||
? "未配置"
|
||
: wechatConfig.AppId.Length > 8
|
||
? $"{wechatConfig.AppId.Substring(0, 8)}***"
|
||
: wechatConfig.AppId;
|
||
Console.WriteLine($"[Program] 微信小程序配置已加载 - AppId: {appIdDisplay}");
|
||
Console.WriteLine($"[Program] 提示:微信配置支持热更新,配置变更后将自动重新加载客户端");
|
||
#endregion
|
||
|
||
#region 注册消息事件后台消费者
|
||
// 注册点赞消息消费者
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.LikeMessageConsumer>();
|
||
|
||
// 注册评论回复消息消费者
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.CommentReplyMessageConsumer>();
|
||
|
||
// 注册自定义消息消费者
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.CustomMessageConsumer>();
|
||
|
||
Console.WriteLine("[Program] 消息事件后台消费者已注册:LikeConsumer, CommentConsumer, CustomConsumer");
|
||
#endregion
|
||
|
||
#region 注册点赞批量同步后台服务
|
||
// 注册点赞批量同步服务
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.LikeBatchSyncService>();
|
||
|
||
Console.WriteLine("[Program] 点赞批量同步后台服务已注册:LikeBatchSyncService");
|
||
#endregion
|
||
|
||
#region 注册浏览批量同步后台服务
|
||
// 注册浏览批量同步服务
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.ViewBatchSyncService>();
|
||
|
||
Console.WriteLine("[Program] 浏览批量同步后台服务已注册:ViewBatchSyncService");
|
||
#endregion
|
||
|
||
#region 注册送花批量同步后台服务
|
||
// 注册送花批量同步服务
|
||
builder.Services.AddHostedService<LiveForum.WebApi.BackgroundServices.FlowerBatchSyncService>();
|
||
|
||
Console.WriteLine("[Program] 送花批量同步后台服务已注册:FlowerBatchSyncService");
|
||
#endregion
|
||
|
||
#region 腾讯云COS配置
|
||
// 绑定腾讯云COS配置(使用 Bind 方法支持自动映射和热更新)
|
||
builder.Services.Configure<TencentCosConfig>(builder.Configuration.GetSection("TENCENT_COS"));
|
||
|
||
// 可选:后处理配置,设置默认值
|
||
builder.Services.PostConfigure<TencentCosConfig>(options =>
|
||
{
|
||
// 设置默认值(如果配置中没有提供)
|
||
if (options.MaxSize <= 0)
|
||
{
|
||
options.MaxSize = 100; // 默认100MB
|
||
}
|
||
|
||
if (options.DurationSecond <= 0)
|
||
{
|
||
options.DurationSecond = 600; // 默认600秒
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(options.Prefixes))
|
||
{
|
||
options.Prefixes = "file"; // 默认前缀
|
||
}
|
||
});
|
||
|
||
var cosConfig = builder.Configuration.GetSection("TENCENT_COS").Get<TencentCosConfig>() ?? new TencentCosConfig();
|
||
Console.WriteLine($"[Program] 腾讯云COS配置已加载 - Region: {cosConfig.Region}, Bucket: {cosConfig.BucketName}");
|
||
Console.WriteLine($"[Program] 提示:COS 配置支持热更新,服务将通过 IOptionsSnapshot 获取最新配置");
|
||
#endregion
|
||
|
||
#region 敏感词检测服务
|
||
// 绑定敏感词配置(使用 Configure 支持热更新)
|
||
builder.Services.Configure<SensitiveWordConfig>(builder.Configuration.GetSection("SensitiveWord"));
|
||
|
||
// 注册敏感词配置为单例(注意:这里使用 IOptions 而不是 IOptionsSnapshot,因为服务本身是单例)
|
||
// 敏感词服务需要稳定的配置引用,配置变更通过 ReloadAllAsync 方法处理
|
||
builder.Services.AddSingleton<SensitiveWordConfig>(sp =>
|
||
{
|
||
var options = sp.GetRequiredService<IOptions<SensitiveWordConfig>>();
|
||
return options.Value;
|
||
});
|
||
|
||
// 注册敏感词服务(单例,只初始化一次)
|
||
builder.Services.AddSingleton<ISensitiveWordService>(sp =>
|
||
{
|
||
var repository = sp.GetRequiredService<IBaseRepository<T_SensitiveWords>>();
|
||
var redisService = sp.GetRequiredService<IRedisService>();
|
||
var config = sp.GetRequiredService<SensitiveWordConfig>();
|
||
var appRootPath = builder.Environment.ContentRootPath; // 应用根目录
|
||
|
||
Console.WriteLine("[Program] ========================================");
|
||
Console.WriteLine("[Program] 正在初始化敏感词服务(单例,只执行一次)...");
|
||
Console.WriteLine($"[Program] 应用根目录: {appRootPath}");
|
||
var service = new LiveForum.Service.SensitiveWord.SensitiveWordService(repository, redisService, config, appRootPath);
|
||
Console.WriteLine("[Program] 敏感词服务初始化完成!");
|
||
Console.WriteLine("[Program] ========================================");
|
||
|
||
return service;
|
||
});
|
||
#endregion
|
||
|
||
#region 应用配置
|
||
// 绑定应用配置(使用 Configure 支持热更新)
|
||
builder.Services.Configure<LiveForum.Model.Dto.Others.AppSettings>(builder.Configuration.GetSection("AppSettings"));
|
||
|
||
// 注册 IOptionsSnapshot 用于 Scoped 服务(每次请求获取最新配置)
|
||
// 这样在 LoginService 等服务中注入时,每次请求都会读取最新配置
|
||
// 注意:由于 LoginService 等服务的生命周期,我们需要使用 IOptionsSnapshot
|
||
|
||
var appSettings = builder.Configuration.GetSection("AppSettings").Get<LiveForum.Model.Dto.Others.AppSettings>() ?? new LiveForum.Model.Dto.Others.AppSettings();
|
||
Console.WriteLine($"[Program] 应用配置已加载 - 默认头像: {(string.IsNullOrEmpty(appSettings.UserDefaultIcon) ? "未配置" : "已配置")}, 默认昵称前缀: {appSettings.UserDefaultName}");
|
||
Console.WriteLine($"[Program] 提示:AppSettings 配置支持热更新,服务将通过 IOptionsSnapshot 获取最新配置");
|
||
#endregion
|
||
|
||
#region AppConfig 缓存管理器
|
||
// 注册 AppConfig 缓存管理器(Scoped,每个请求一个实例,实例字段缓存)
|
||
builder.Services.AddScoped<LiveForum.Code.SystemCache.AppConfigManage.AppConfigCacheManager>(sp =>
|
||
{
|
||
var configuration = sp.GetRequiredService<IConfiguration>();
|
||
return new LiveForum.Code.SystemCache.AppConfigManage.AppConfigCacheManager(configuration);
|
||
});
|
||
#endregion
|
||
|
||
var app = builder.Build();
|
||
|
||
// 预热敏感词服务(触发词库加载)
|
||
using (var scope = app.Services.CreateScope())
|
||
{
|
||
var sensitiveWordService = scope.ServiceProvider.GetRequiredService<ISensitiveWordService>();
|
||
Console.WriteLine("[Program] 敏感词服务已预热");
|
||
}
|
||
|
||
#region AgileConfig 配置变更监听
|
||
// 监听 AgileConfig 配置变更事件
|
||
var configClient = app.Services.GetService<IConfigClient>();
|
||
if (configClient != null)
|
||
{
|
||
Console.WriteLine("[Program] 正在注册 AgileConfig 配置变更监听器...");
|
||
// event Action<ConfigChangedArg> ConfigChanged; 只有一个参数
|
||
configClient.ConfigChanged += (args) =>
|
||
{
|
||
try
|
||
{
|
||
var action = args?.Action ?? "Unknown";
|
||
var key = args?.Key ?? "(全局重新加载)";
|
||
|
||
Console.WriteLine($"[AgileConfig] 检测到配置变更: {action} - Key: {key}");
|
||
|
||
// 如果 Key 为空或是 reload 操作,说明是全局配置重新加载
|
||
// 需要检查所有配置是否变更
|
||
var isGlobalReload = string.IsNullOrEmpty(args?.Key) || args.Action == "reload";
|
||
|
||
// 检查敏感词配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("SensitiveWord") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] 检查敏感词配置是否变更...");
|
||
|
||
Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
using var scope = app.Services.CreateScope();
|
||
var sensitiveWordService = scope.ServiceProvider.GetRequiredService<ISensitiveWordService>();
|
||
|
||
// 获取最新的配置
|
||
var newConfig = app.Configuration.GetSection("SensitiveWord").Get<SensitiveWordConfig>();
|
||
|
||
// 调用重新加载(内部会对比配置,只有真正变化才重新加载)
|
||
await sensitiveWordService.ReloadAllAsync(newConfig);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 敏感词服务重新加载失败: {ex.Message}");
|
||
}
|
||
}).Wait();
|
||
}
|
||
|
||
// 检查应用配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("AppSettings") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] 应用配置已变更");
|
||
var newAppSettings = app.Configuration.GetSection("AppSettings").Get<LiveForum.Model.Dto.Others.AppSettings>();
|
||
if (newAppSettings != null)
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 新的默认头像: {(string.IsNullOrEmpty(newAppSettings.UserDefaultIcon) ? "未配置" : "已配置")}, 默认昵称前缀: {newAppSettings.UserDefaultName}");
|
||
Console.WriteLine($"[AgileConfig] 提示:新注册用户将使用新配置");
|
||
}
|
||
}
|
||
|
||
// 检查 AppConfig 配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("AppConfig") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] AppConfig 配置已变更");
|
||
Console.WriteLine("[AgileConfig] 提示:由于 AppConfigCacheManager 为 Scoped 服务,下次请求将自动使用新配置");
|
||
}
|
||
|
||
// 检查腾讯云COS配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("TENCENT_COS") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] 腾讯云COS配置已变更");
|
||
var newCosConfig = app.Configuration.GetSection("TENCENT_COS").Get<TencentCosConfig>();
|
||
if (newCosConfig != null)
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 新的存储桶: {newCosConfig.BucketName}, 最大上传: {newCosConfig.MaxSize}MB");
|
||
Console.WriteLine($"[AgileConfig] 提示:下次文件上传将使用新配置(通过 IOptionsSnapshot 自动获取)");
|
||
}
|
||
}
|
||
|
||
// 检查微信小程序配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("Wechat") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] 微信小程序配置已变更,正在重新加载客户端...");
|
||
|
||
Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
using var scope = app.Services.CreateScope();
|
||
var manager = scope.ServiceProvider.GetRequiredService<LiveForum.IService.Others.IWechatApiClientManager>();
|
||
|
||
var success = manager.Reload();
|
||
if (success)
|
||
{
|
||
var newAppId = manager.GetCurrentAppId();
|
||
var appIdDisplay = string.IsNullOrEmpty(newAppId)
|
||
? "未配置"
|
||
: newAppId.Length > 8
|
||
? $"{newAppId.Substring(0, 8)}***"
|
||
: newAppId;
|
||
Console.WriteLine($"[AgileConfig] 微信 API 客户端重新加载成功 - 新 AppId: {appIdDisplay}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 微信 API 客户端重新加载失败,请检查配置是否正确");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 微信 API 客户端重新加载失败: {ex.Message}");
|
||
}
|
||
}).Wait();
|
||
}
|
||
|
||
// 检查 JWT 配置是否变更
|
||
if (isGlobalReload || args.Key?.StartsWith("JwtTokenConfig") == true)
|
||
{
|
||
Console.WriteLine("[AgileConfig] JWT 配置已变更");
|
||
var newJwtConfig = app.Configuration.GetSection("JwtTokenConfig").Get<LiveForum.Code.JwtInfrastructure.JwtTokenConfig>();
|
||
if (newJwtConfig != null)
|
||
{
|
||
var secretDisplay = string.IsNullOrEmpty(newJwtConfig.Secret)
|
||
? "未配置"
|
||
: newJwtConfig.Secret.Length > 8
|
||
? $"{newJwtConfig.Secret.Substring(0, 8)}***"
|
||
: newJwtConfig.Secret;
|
||
Console.WriteLine($"[AgileConfig] 新的 JWT 配置 - Issuer: {newJwtConfig.Issuer}, Audience: {newJwtConfig.Audience}, Secret: {secretDisplay}, Expiration: {newJwtConfig.AccessTokenExpiration}分钟");
|
||
Console.WriteLine($"[AgileConfig] 提示:JWT 配置支持热更新,新生成的 token 将使用新配置(通过 IOptionsMonitor 自动获取)");
|
||
Console.WriteLine($"[AgileConfig] 注意:已签发的 token 仍使用旧配置验证,如需完全切换,建议重启应用");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[AgileConfig] 处理配置变更失败: {ex.Message}");
|
||
}
|
||
};
|
||
|
||
Console.WriteLine("[AgileConfig] 配置变更监听器注册完成!");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("[AgileConfig] 未检测到 AgileConfig 客户端,配置变更监听功能未启用");
|
||
}
|
||
#endregion
|
||
|
||
//引入中间件
|
||
app.UseMiddlewareAll();
|
||
// Configure the HTTP request pipeline.
|
||
if (app.Environment.IsDevelopment() || true)
|
||
{
|
||
app.UseSwagger();
|
||
app.UseSwaggerUI(c =>
|
||
{
|
||
|
||
c.EnableDeepLinking();
|
||
c.DefaultModelsExpandDepth(3);
|
||
c.DefaultModelExpandDepth(3);
|
||
c.EnableFilter("true");
|
||
//c.RoutePrefix = string.Empty;
|
||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
|
||
// 使用自定义CSS
|
||
//c.InjectStylesheet("/custom.css");
|
||
});
|
||
|
||
}
|
||
|
||
// 配置静态文件目录,允许访问uploads目录
|
||
app.UseStaticFiles(new StaticFileOptions
|
||
{
|
||
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(
|
||
Path.Combine(app.Environment.ContentRootPath, "wwwroot")),
|
||
RequestPath = "",
|
||
//OnPrepareResponse = ctx =>
|
||
//{
|
||
// // 设置静态文件缓存
|
||
// ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=31536000");
|
||
//}
|
||
});
|
||
|
||
app.UseRouting();
|
||
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
// 响应缓存中间件(需要在路由解析后执行,可以获取Endpoint信息)
|
||
app.UseResponseCacheMiddleware();
|
||
|
||
app.MapControllers();
|
||
|
||
#region Hangfire Dashboard
|
||
// 配置Hangfire Dashboard(任务管理UI)
|
||
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
||
{
|
||
DashboardTitle = "定时任务管理"
|
||
// 不添加认证(按需求)
|
||
});
|
||
#endregion
|
||
|
||
//
|
||
app.UseAppRequest("论坛社区");
|
||
|
||
#region 注册定时任务
|
||
// 注册定时任务:每月1号凌晨3点执行主播送花数量重置任务
|
||
// Hangfire会自动从DI容器(Autofac)中解析IScheduledJobService服务
|
||
//RecurringJob.AddOrUpdate<LiveForum.IService.Others.IScheduledJobService>(
|
||
// "streamer-flower-reset", // 任务ID(唯一标识)
|
||
// job => job.ResetStreamerFlowerCountAsync(),
|
||
// "0 3 1 * *", // Cron表达式:每月1号凌晨3点
|
||
// new RecurringJobOptions
|
||
// {
|
||
// TimeZone = TimeZoneInfo.Local // 使用本地时区
|
||
// });
|
||
|
||
//Console.WriteLine("[Program] Hangfire定时任务已注册:streamer-flower-reset(每月1号凌晨3点执行)");
|
||
#endregion
|
||
|
||
app.Run();
|