live-forum/server/webapi/LiveForum/LiveForum.WebApi/Program.cs
2026-03-24 11:27:37 +08:00

642 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();