You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
25 KiB

using AutoMapper;
using Hangfire;
using Hangfire.Dashboard.BasicAuthorization;
using Hangfire.Redis;
using log4net;
using log4net.Repository;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Pursue.Extension.MongoDB;
using Senparc.CO2NET;
using Senparc.CO2NET.RegisterServices;
using Senparc.Weixin.MP.Containers;
using Senparc.Weixin.RegisterServices;
using Swashbuckle.AspNetCore.Filters;
using System;
using System.IO;
using System.Linq;
using System.Runtime.Loader;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using Znyc.Cloudcar.Admin.AspNetCore.Common;
using Znyc.Cloudcar.Admin.AspNetCore.Mvc;
using Znyc.Cloudcar.Admin.AspNetCore.Mvc.Filter;
using Znyc.Cloudcar.Admin.Commons.Cache;
using Znyc.Cloudcar.Admin.Commons.Core.App;
using Znyc.Cloudcar.Admin.Commons.Cos;
using Znyc.Cloudcar.Admin.Commons.DbContextCore;
using Znyc.Cloudcar.Admin.Commons.Extensions;
using Znyc.Cloudcar.Admin.Commons.Helpers;
using Znyc.Cloudcar.Admin.Commons.IDbContext;
using Znyc.Cloudcar.Admin.Commons.Linq;
using Znyc.Cloudcar.Admin.Commons.Log;
using Znyc.Cloudcar.Admin.Commons.Module;
using Znyc.Cloudcar.Admin.Commons.Options;
using Znyc.Cloudcar.Admin.Hangfire;
using Znyc.Cloudcar.Admin.MongoDb;
using Znyc.Cloudcar.Admin.MongoDb.Core.IRepositorys;
using Znyc.Cloudcar.Admin.MongoDb.Core.Repositorys;
using Znyc.Cloudcar.Admin.WebApi.Auth;
using Znyc.Cloudcar.Admin.WebApi.Config;
namespace Znyc.Cloudcar.Admin.WebApi
{
/// <summary>
/// </summary>
public class Startup
{
private IApiVersionDescriptionProvider apiVersionProvider; //api接口版本控制
private IMvcBuilder mvcBuilder;
private string targetPath = string.Empty;
/// <summary>
/// </summary>
/// <param name="configuration"></param>
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
//初始化log4net
LoggerRepository = LogManager.CreateRepository("NETCoreRepository");
Log4NetHelper.SetConfig(LoggerRepository, "log4net.config");
if (env.IsProduction())
{
Log4NetHelper.SetConfig(LoggerRepository, "log4net.Production.config");
}
}
/// <summary>
/// </summary>
public static ILoggerRepository LoggerRepository { get; set; }
/// <summary>
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddHttpContextAccessor();
//如果部署在linux系统上,需要加上下面的配置:
services.Configure<KestrelServerOptions>(options => options.AllowSynchronousIO = true);
services.AddOptions();
services.Configure<IISServerOptions>(options => options.AllowSynchronousIO = true);
#region Swagger Api文档
// Api多版本版本配置
services.AddApiVersioning(o =>
{
o.ReportApiVersions = true; //是否在请求头中返回受支持的版本信息。
o.ApiVersionReader =
new HeaderApiVersionReader("api-version"); ////版本信息放到header ,不写在不配置路由的情况下,版本信息放到response url 中
o.AssumeDefaultVersionWhenUnspecified = true; //请求没有指明版本的情况下是否使用默认的版本。
o.DefaultApiVersion = new ApiVersion(1, 0); //默认的版本号。
}).AddVersionedApiExplorer(option =>
{
// 版本名的格式:v+版本号
option.GroupNameFormat = "'v'V";
option.AssumeDefaultVersionWhenUnspecified = true;
});
//获取webapi版本信息,用于swagger多版本支持
apiVersionProvider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
services.AddSwaggerGen(options =>
{
foreach (ApiVersionDescription description in apiVersionProvider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName,
new OpenApiInfo
{
Title = $"{Configuration.GetSection("SwaggerDoc:Title").Value}v{description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Description = Configuration.GetSection("SwaggerDoc:Description").Value
}
);
}
Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.xml").ToList().ForEach(file =>
{
options.IncludeXmlComments(file, true);
});
options.DocumentFilter<HiddenApiFilter>(); // 在接口类、方法标记属性 [HiddenApi],可以阻止【Swagger文档】生成
//给api添加token令牌证书
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
Name = "Authorization", //jwt默认的参数名称
In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
//添加安全请求
options.AddSecurityRequirement(
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
}
);
//开启加权锁
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.OperationFilter<SecurityRequirementsOperationFilter>();
});
#endregion Swagger Api文档
#region 全局设置跨域访问
//允许所有跨域访问,测试用
services.AddCors(options => options.AddPolicy("Cors",
policy => policy.WithOrigins("*").AllowAnyHeader().AllowAnyMethod()));
// 跨域设置 建议正式环境
//services.AddCors(options => options.AddPolicy("ZnycCors",
// policy => policy.WithOrigins(Configuration.GetSection("AppSetting:AllowOrigins").Value.Split(',', StringSplitOptions.RemoveEmptyEntries)).AllowAnyHeader().AllowAnyMethod()));
#endregion 全局设置跨域访问
#region MiniProfiler
services.AddMiniProfiler(options => { options.RouteBasePath = "/profiler"; }).AddEntityFramework();
#endregion MiniProfiler
#region 控制器
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
//设置时间格式
options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
options.JsonSerializerOptions.Converters.Add(new DateTimeNullableConverter());
//设置bool获取格式
options.JsonSerializerOptions.Converters.Add(new BooleanJsonConverter());
//设置Decimal获取格式
options.JsonSerializerOptions.Converters.Add(new DecimalJsonConverter());
//设置数字
options.JsonSerializerOptions.Converters.Add(new IntJsonConverter());
options.JsonSerializerOptions.PropertyNamingPolicy = new UpperFirstCaseNamingPolicy();
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
mvcBuilder = services.AddMvc(option =>
{
//option.Filters.Add<FunctionAuthorizationFilter>();
option.Filters.Add(new ExceptionHandlingAttribute());
// option.Filters.Add<ActionFilter>();
}).SetCompatibilityVersion(CompatibilityVersion.Latest).AddRazorRuntimeCompilation();
services.AddMvcCore()
.AddAuthorization().AddApiExplorer();
#endregion 控制器
services.AddSignalR(); //使用 SignalR
InitIoC(services);
services.AddCors(options => options.AddPolicy("ZnycCors",
policy => policy.WithOrigins("*").AllowAnyHeader().AllowAnyMethod()));
services.AddSenparcWeixinServices(Configuration);
services.TryAddSingleton<IApiHandler, ApiHandler>();
#region Handfire
services.AddHangfire(x => x.UseRedisStorage(Configuration.GetSection("CacheProvider:Redis_ConnectionString").Value));
services.AddHangfireServer(options =>
{
options.Queues = new[] { HangFireQueuesConfig.@default.ToString(), HangFireQueuesConfig.apis.ToString(), HangFireQueuesConfig.web.ToString(), HangFireQueuesConfig.recurring.ToString() };
options.ServerTimeout = TimeSpan.FromMinutes(4);
options.SchedulePollingInterval = TimeSpan.FromSeconds(15);//秒级任务需要配置短点,一般任务可以配置默认时间,默认15秒
options.ShutdownTimeout = TimeSpan.FromMinutes(30); //超时时间
options.WorkerCount = Math.Max(Environment.ProcessorCount, 20); //工作线程数,当前允许的最大线程,默认20
});
#endregion
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IOptions<SenparcSetting> senparcSetting)
{
if (app != null)
{
app.UseStaticHttpContextAccessor();
IServiceProvider provider = app.ApplicationServices;
AutoMapperService.UsePack(provider);
//加载插件应用
LoadMoudleApps(env);
app.UseMiniProfiler();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (ApiVersionDescription description in apiVersionProvider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
$"{Configuration.GetSection("SwaggerDoc:Title").Value + description.GroupName.ToUpperInvariant()}");
options.RoutePrefix = string.Empty; //这里主要是不需要再输入swagger这个默认前缀
}
});
app.Use((context, next) =>
{
context.Request.EnableBuffering();
return next();
});
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
//跨域
app.UseMiddleware<CorsMiddleware>();
app.UseCors("Cors");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute("default", "api/{controller=Home}/{action=Index}/{id?}");
});
app.UseStatusCodePages();
#region WxSdk 注册
IRegisterService register = RegisterService.Start(senparcSetting.Value)
.UseSenparcGlobal();
register.UseSenparcGlobal(true);
AccessTokenContainer.Register("wx07c574aca93ae8d9", "466411f6865d130f50cc48ae1566de99");
#endregion WxSdk 注册
#region Hangfire
var filter = new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
SslRedirect = false,
// Require secure connection for dashboard
RequireSsl = false,
// Case sensitive login checking
LoginCaseSensitive = false,
// Users
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login = Configuration.GetSection("Hangfire:Login").Value,
PasswordClear = Configuration.GetSection("Hangfire:Password").Value
}
}
});
var options = new DashboardOptions
{
AppPath = "/",//返回时跳转的地址
DisplayStorageConnectionString = false,//是否显示数据库连接信息
Authorization = new[]
{
filter
},
IsReadOnlyFunc = Context =>
{
return false;//是否只读面板
}
};
app.UseHangfireDashboard("/admin_job", options);
HangfireDispose.HangfireService();
#endregion
}
}
/// <summary>
/// IoC初始化
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
private void InitIoC(IServiceCollection services)
{
#region 缓存
CacheProvider cacheProvider = new CacheProvider
{
IsUseRedis = Configuration.GetSection("CacheProvider:UseRedis").Value.ToBool(),
ConnectionString = Configuration.GetSection("CacheProvider:Redis_ConnectionString").Value,
InstanceName = Configuration.GetSection("CacheProvider:Redis_InstanceName").Value
};
JsonSerializerOptions options = new JsonSerializerOptions();
options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.WriteIndented = true;
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.AllowTrailingCommas = true;
//设置时间格式
options.Converters.Add(new DateTimeJsonConverter());
options.Converters.Add(new DateTimeNullableConverter());
//设置bool获取格式
options.Converters.Add(new BooleanJsonConverter());
//设置数字
options.Converters.Add(new IntJsonConverter());
options.PropertyNamingPolicy = new UpperFirstCaseNamingPolicy();
options.PropertyNameCaseInsensitive = true; //忽略大小写
//判断是否使用Redis,如果不使用 Redis就默认使用 MemoryCache
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
if (cacheProvider.IsUseRedis)
{
//Use Redis
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = cacheProvider.ConnectionString;
options.InstanceName = cacheProvider.InstanceName;
});
services.AddSingleton(typeof(ICacheService), new RedisCacheService(new RedisCacheOptions
{
Configuration = cacheProvider.ConnectionString,
InstanceName = cacheProvider.InstanceName
}, options));
services.Configure<DistributedCacheEntryOptions>(option =>
option.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)); //设置Redis缓存有效时间为5分钟。
}
else
{
//Use MemoryCache
services.AddSingleton<IMemoryCache>(factory =>
{
MemoryCache cache = new MemoryCache(new MemoryCacheOptions());
return cache;
});
services.AddSingleton<ICacheService, MemoryCacheService>();
services.Configure<MemoryCacheEntryOptions>(
options => options.AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(5)); //设置MemoryCache缓存有效时间为5分钟
}
services.AddTransient<MemoryCacheService>();
services.AddMemoryCache(); // 启用MemoryCache
services.AddSingleton(cacheProvider); //注册缓存配置
#endregion 缓存
#region 身份认证授权
IConfigurationSection jwtConfig = Configuration.GetSection("Jwt");
JwtOption jwtOption = new JwtOption
{
Issuer = jwtConfig["Issuer"],
Expiration = Convert.ToInt16(jwtConfig["Expiration"]),
Secret = jwtConfig["Secret"],
Audience = jwtConfig["Audience"],
refreshJwtTime = Convert.ToInt16(jwtConfig["refreshJwtTime"])
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
;
}).AddJwtBearer(jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOption.Secret)), //秘钥
ValidateIssuer = true,
ValidIssuer = jwtOption.Issuer,
ValidateAudience = true,
ValidAudience = jwtOption.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
services.AddSingleton(jwtOption); //注册配置
#endregion 身份认证授权
services.AddAutoScanInjection(); //自动化注入仓储和服务
services.AddTransient<IDbContextCore, MySqlDbContext>(); //注入EF上下文
#region automapper
System.Collections.Generic.List<System.Reflection.Assembly> myAssembly = RuntimeHelper.GetAllZnycAssemblies().ToList();
services.AddAutoMapper(myAssembly);
services.AddTransient<IMapper, Mapper>();
#endregion automapper
services
.AddMongoDBContext<MongoContext>(options =>
{
options.UseMongo(Configuration.GetSection("MongoConnection:ConnectionString").Value,
Configuration.GetSection("MongoConnection:Database").Value);
});
services
.AddMongoDbService<MongoContext>();
services.AddTransient<IGpsRealTimeRepository, GpsRealTimeRepository>();
#region Cos
CosOptions cosOptions = new CosOptions
{
SecretId = Configuration.GetSection("CosOptions:SecretId").Value,
SecretKey = Configuration.GetSection("CosOptions:SecretKey").Value,
Bucket = Configuration.GetSection("CosOptions:Bucket").Value,
Region = Configuration.GetSection("CosOptions:Region").Value,
AllowPrefix = Configuration.GetSection("CosOptions:AllowPrefix").Value,
DurationSeconds = Configuration.GetSection("CosOptions:DurationSeconds").Value.ToInt(),
AllowActions = new string[] { "name/cos:PutObject",
// 表单上传、小程序上传
"name/cos:PostObject",
// 分片上传
"name/cos:InitiateMultipartUpload",
"name/cos:ListMultipartUploads",
"name/cos:ListParts",
"name/cos:UploadPart",
"name/cos:CompleteMultipartUpload" }
};
services.AddSingleton(cosOptions); //注册缓存配置
#endregion
App.Services = services;
services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
}
/// <summary>
/// 加载模块应用
/// </summary>
/// <param name="env"></param>
private void LoadMoudleApps(IWebHostEnvironment env)
{
// 定位到插件应用目录 Apps
Microsoft.Extensions.FileProviders.IFileInfo apps = env.ContentRootFileProvider.GetFileInfo("Apps");
if (!Directory.Exists(apps.PhysicalPath))
{
return;
}
// 把 Apps 下的动态库拷贝一份来运行,
// 使 Apps 中的动态库不会在运行时被占用(以便重新编译)
string shadows = targetPath = PrepareShadowCopies();
// 从 Shadow Copy 目录加载 Assembly 并注册到 Mvc 中
LoadFromShadowCopies(shadows);
string PrepareShadowCopies()
{
// 准备 Shadow Copy 的目标目录
string target = Path.Combine(env.ContentRootPath, "app_data", "apps-cache");
Directory.CreateDirectory(target);
// 找到插件目录下 bin 目录中的 .dll,拷贝
Directory.EnumerateDirectories(apps.PhysicalPath)
.Select(path => Path.Combine(path, "bin"))
.Where(Directory.Exists)
.SelectMany(bin => Directory.EnumerateFiles(bin, "*.dll"))
.ForEach(dll => File.Copy(dll, Path.Combine(target, Path.GetFileName(dll)), true));
return target;
}
void LoadFromShadowCopies(string targetPath)
{
foreach (string dll in Directory.GetFiles(targetPath, "*.dll"))
{
try
{
//解决插件还引用其他主程序没有引用的第三方dll问题System.IO.FileNotFoundException
AssemblyLoadContext.Default.LoadFromAssemblyPath(dll);
}
catch (Exception ex)
{
//非.net程序集类型的dll关联load时会报错,这里忽略就可以
Log4NetHelper.Error(ex.Message);
}
}
// 从 Shadow Copy 目录加载 Assembly 并注册到 Mvc 中
System.Collections.Generic.IEnumerable<System.Reflection.Assembly> groups = Directory.EnumerateFiles(targetPath, "Znyc.Cloudcar.Admin.*App.Api.dll")
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);
// 直接加载到为 ApplicationPart
groups.ForEach(mvcBuilder.AddApplicationPart);
}
}
}
}