添加项目文件。
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class AddDbContextExtensions
|
||||
{
|
||||
public static IServiceCollection AddAllDbContexts(this IServiceCollection services, Action<DbContextOptionsBuilder> action, IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
//AddDbContextPool不支持DbContext注入其他对象,而且使用不当有内存暴涨的问题,因此不用AddDbContextPool
|
||||
Type[] types = new Type[] { typeof(IServiceCollection), typeof(Action<DbContextOptionsBuilder>), typeof(ServiceLifetime), typeof(ServiceLifetime) };
|
||||
var methodAddDbContext = typeof(EntityFrameworkServiceCollectionExtensions)
|
||||
.GetMethod(nameof(EntityFrameworkServiceCollectionExtensions.AddDbContext), 1, types);
|
||||
foreach (var asmToLoad in assemblies)
|
||||
{
|
||||
Type[] typesInAsm = asmToLoad.GetTypes();
|
||||
//Register DbContext
|
||||
//GetTypes() include public/protected ones
|
||||
//GetExportedTypes only include public ones
|
||||
//so that XXDbContext in Agrregation can be internal to keep insulated
|
||||
foreach (var dbCtxType in typesInAsm
|
||||
.Where(t => !t.IsAbstract && typeof(DbContext).IsAssignableFrom(t)))
|
||||
{
|
||||
//similar to serviceCollection.AddDbContextPool<ECDictDbContext>(opt=>new DbContextOptionsBuilder(dbCtxOpt));
|
||||
var methodGenericAddDbContext = methodAddDbContext.MakeGenericMethod(dbCtxType);
|
||||
methodGenericAddDbContext.Invoke(null, new object[] { services, action, ServiceLifetime.Scoped, ServiceLifetime.Scoped });
|
||||
}
|
||||
}
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using IM.ASPNETCore;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class ApplicationBuilderExtension
|
||||
{
|
||||
public static IApplicationBuilder UseAppDefault(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
app.UseForwardedHeaders();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public class ConnectionStringOptions
|
||||
{
|
||||
public string DefaultConnection { get; init; }
|
||||
public string Redis { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public class ConsulOption
|
||||
{
|
||||
public string Url { get; private set; } = "http://192.168.5.100:8500";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public class CorsOptions
|
||||
{
|
||||
public string[] Origins { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class DbContextOptionsBuilderFactory
|
||||
{
|
||||
public static DbContextOptionsBuilder<TDbContext> Create<TDbContext>()
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
var connStr = Environment.GetEnvironmentVariable("DefaultDB_ConnStr");
|
||||
var optionsBuilder = new DbContextOptionsBuilder<TDbContext>();
|
||||
//optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=YouzackVNextDB;User ID=sa;Password=dLLikhQWy5TBz1uM;");
|
||||
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
|
||||
return optionsBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class GrpcExtension
|
||||
{
|
||||
public static IServiceCollection AddAllGrpcServer(this IServiceCollection services)
|
||||
{
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
// 开启详细错误(开发环境很有用,生产环境可结合配置读取)
|
||||
options.EnableDetailedErrors = true;
|
||||
|
||||
// 限制最大接收和发送的消息大小 (例如 10MB,防止大包攻击)
|
||||
options.MaxReceiveMessageSize = 10 * 1024 * 1024;
|
||||
options.MaxSendMessageSize = 10 * 1024 * 1024;
|
||||
|
||||
// TODO: 未来你可以在这里添加全局异常拦截器 (Interceptor)
|
||||
// options.Interceptors.Add<GlobalGrpcExceptionInterceptor>();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IEndpointRouteBuilder MapAllGrpcServer(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
// 获取调用此方法的程序集(即具体的微服务项目,如 MessageService)
|
||||
var assembly = Assembly.GetCallingAssembly();
|
||||
|
||||
// 获取 MapGrpcService<T> 的方法反射信息
|
||||
var mapGrpcServiceMethod = typeof(GrpcEndpointRouteBuilderExtensions)
|
||||
.GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||
.First(m => m.Name == "MapGrpcService" && m.GetGenericArguments().Length == 1);
|
||||
|
||||
// 查找当前项目中所有继承了 gRPC 生成的 Base 类的具体实现类
|
||||
// gRPC 生成的基类通常以 "Base" 结尾,例如 ConversationInternalBase
|
||||
var grpcTypes = assembly.GetTypes()
|
||||
.Where(t => t.IsClass
|
||||
&& !t.IsAbstract
|
||||
&& t.BaseType != null
|
||||
&& t.BaseType.Name.EndsWith("Base"))
|
||||
.ToList();
|
||||
|
||||
// 循环并动态调用 MapGrpcService<T>
|
||||
foreach (var type in grpcTypes)
|
||||
{
|
||||
var genericMethod = mapGrpcServiceMethod.MakeGenericMethod(type);
|
||||
genericMethod.Invoke(null, new object[] { endpoints });
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
|
||||
<PackageReference Include="MassTransit.RabbitMQ" Version="9.1.0" />
|
||||
<PackageReference Include="MediatR" Version="14.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.25" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.7" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
<PackageReference Include="RedLock.net" Version="2.3.2" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.12.14" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Winton.Extensions.Configuration.Consul" Version="3.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||
<ProjectReference Include="..\IM.ASPNETCore\IM.ASPNETCore.csproj" />
|
||||
<ProjectReference Include="..\IM.Jwt\IM.Jwt.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
using IM.Commons;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class ModuleInitializerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 每个项目中都可以自己写一些实现了IModuleInitializer接口的类,在其中注册自己需要的服务,这样避免所有内容到入口项目中注册
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="assemblies"></param>
|
||||
public static IServiceCollection RunModuleInitializers(this IServiceCollection services,
|
||||
IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
foreach (var asm in assemblies)
|
||||
{
|
||||
Type[] types = asm.GetTypes();
|
||||
var moduleInitializerTypes = types.Where(t => !t.IsAbstract && typeof(IModuleInitializer).IsAssignableFrom(t));
|
||||
foreach (var implType in moduleInitializerTypes)
|
||||
{
|
||||
var initializer = (IModuleInitializer?)Activator.CreateInstance(implType);
|
||||
if (initializer == null)
|
||||
{
|
||||
throw new ApplicationException($"Cannot create ${implType}");
|
||||
}
|
||||
initializer.Initialize(services);
|
||||
}
|
||||
}
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class RabbitMqExtension
|
||||
{
|
||||
public static IServiceCollection AddRabbitMq(this IServiceCollection services, RabbitMqOptions options, IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
|
||||
var safeAssemblies = assemblies
|
||||
.Where(a => a.FullName != null &&
|
||||
!a.FullName.StartsWith("MassTransit", StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.FullName.StartsWith("System", StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.FullName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
|
||||
return services.AddMassTransit(x =>
|
||||
{
|
||||
x.AddConsumers(safeAssemblies);
|
||||
|
||||
x.UsingRabbitMq((context, cfg) =>
|
||||
{
|
||||
cfg.Host(options.Host, (ushort)options.Port, "/", c =>
|
||||
{
|
||||
c.Username(options.Username);
|
||||
c.Password(options.Password);
|
||||
});
|
||||
|
||||
cfg.ConfigureEndpoints(context);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public class RabbitMqOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string QuequeName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public class StorageOptions
|
||||
{
|
||||
public string ProviderCode { get; init; } = default!;
|
||||
|
||||
public StorageProviderType ProviderType { get; init; }
|
||||
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
public string Bucket { get; init; } = default!;
|
||||
|
||||
public string Region { get; init; } = default!;
|
||||
|
||||
public string? Endpoint { get; init; }
|
||||
|
||||
public string? PublicBaseUrl { get; init; }
|
||||
|
||||
public string? AccessKeyId { get; init; }
|
||||
|
||||
public string? AccessKeySecret { get; init; }
|
||||
|
||||
public string? LocalRootPath { get; init; }
|
||||
|
||||
public string? LocalUploadApiBaseUrl { get; init; }
|
||||
|
||||
public TimeSpan UploadUrlExpiresIn { get; init; } = TimeSpan.FromMinutes(15);
|
||||
|
||||
public TimeSpan DownloadUrlExpiresIn { get; init; } = TimeSpan.FromMinutes(30);
|
||||
|
||||
public long MaxObjectSizeBytes { get; init; } = 1024L * 1024 * 1024;
|
||||
|
||||
public int MinPartSizeBytes { get; init; } = 5 * 1024 * 1024;
|
||||
|
||||
public int MaxPartCount { get; init; } = 10_000;
|
||||
}
|
||||
public enum StorageProviderType
|
||||
{
|
||||
Local = 1,
|
||||
AwsS3 = 2,
|
||||
AliyunOss = 3,
|
||||
TencentCos = 4,
|
||||
Minio = 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class SwaggerGenExtension
|
||||
{
|
||||
public static IServiceCollection AddSwaggerGenOpt(this IServiceCollection services)
|
||||
{
|
||||
return services.AddSwaggerGen(options =>
|
||||
{
|
||||
// 1. 定义安全定义 (Security Definition)
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Description = "请输入 JWT Token,不需要输入 'Bearer ' 前缀,系统会自动添加。"
|
||||
});
|
||||
|
||||
// 2. 开启全局安全要求 (Security Requirement)
|
||||
// 这样 Swagger UI 所有的接口都会出现锁头图标
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.AspNetCore;
|
||||
using IM.ASPNETCore;
|
||||
using IM.Commons;
|
||||
using IM.Jwt;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RedLockNet;
|
||||
using RedLockNet.SERedis;
|
||||
using RedLockNet.SERedis.Configuration;
|
||||
using StackExchange.Redis;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Winton.Extensions.Configuration.Consul;
|
||||
|
||||
namespace IM.InitCommon
|
||||
{
|
||||
public static class WebApplicationBuilderExtensions
|
||||
{
|
||||
public static void ConfigureDbConfiguration(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Host.ConfigureAppConfiguration((hostCtx, configbuilder) =>
|
||||
{
|
||||
var env = hostCtx.HostingEnvironment;
|
||||
string serviceName = env.ApplicationName.Replace(".WebApi", "");
|
||||
|
||||
ConsulOption consulOption = hostCtx.Configuration.GetSection("ConsulOptions").Get<ConsulOption>() ?? new ConsulOption();
|
||||
string consulKey = $"IM/{env.EnvironmentName}/appsettings.json";
|
||||
string serviceConsulKey = $"IM/{env.EnvironmentName}/{serviceName}/appsettings.json";
|
||||
configbuilder.AddConsul(
|
||||
consulKey,
|
||||
options =>
|
||||
{
|
||||
options.ConsulConfigurationOptions = cco =>
|
||||
{
|
||||
cco.Address = new Uri(consulOption.Url);
|
||||
};
|
||||
//options.Optional = true; // 如果本地开发没开 Consul,不报错,继续运行
|
||||
options.ReloadOnChange = true; // 开启热更新!Consul 里改了,程序立马生效
|
||||
//options.OnLoadException = exceptionContext => { exceptionContext.Ignore = true; };
|
||||
}
|
||||
);
|
||||
|
||||
configbuilder.AddConsul(serviceConsulKey, options =>
|
||||
{
|
||||
options.ConsulConfigurationOptions = cco => { cco.Address = new Uri(consulOption.Url); };
|
||||
options.Optional = true;
|
||||
options.ReloadOnChange = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigExtraServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
|
||||
var assemblies = ReflectionHelper.GetAllReferencedAssemblies();
|
||||
|
||||
services.RunModuleInitializers(assemblies);
|
||||
|
||||
services.AddAutoMapper(cfg => { }, assemblies);
|
||||
|
||||
//数据库配置
|
||||
var conOpt = configuration.GetSection("ConnectionStrings").Get<ConnectionStringOptions>();
|
||||
builder.Services.Configure<GrpcOptions>(builder.Configuration.GetSection("GrpcConfigs"));
|
||||
|
||||
services.AddMediatR(ctx =>
|
||||
{
|
||||
ctx.RegisterServicesFromAssemblies([.. assemblies]);
|
||||
});
|
||||
|
||||
var rabbitmqOpt = configuration.GetSection("RabbitMQOptions").Get<RabbitMqOptions>();
|
||||
|
||||
services.AddRabbitMq(rabbitmqOpt, assemblies);
|
||||
|
||||
services.AddAllDbContexts(options =>
|
||||
{
|
||||
options.UseMySql(conOpt.DefaultConnection, ServerVersion.AutoDetect(conOpt.DefaultConnection));
|
||||
}, assemblies);
|
||||
|
||||
services.Configure<SwaggerGenOptions>(c =>
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
services.Configure<ApiBehaviorOptions>(options =>
|
||||
{
|
||||
// 禁用默认的自动 400 响应
|
||||
options.SuppressModelStateInvalidFilter = true;
|
||||
});
|
||||
|
||||
|
||||
|
||||
JwtOptions jwtOptions = configuration.GetSection("Jwt").Get<JwtOptions>();
|
||||
|
||||
services.AddJwt(jwtOptions);
|
||||
|
||||
services.Configure<MvcOptions>(m =>
|
||||
{
|
||||
m.Filters.Add<UnitOfWorkFilter>();
|
||||
m.Filters.Add<ValidatorFilter>();
|
||||
});
|
||||
|
||||
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
|
||||
|
||||
services.Configure<StorageOptions>(configuration.GetSection("StorageOptions"));
|
||||
|
||||
//模型校验
|
||||
services.AddValidatorsFromAssemblies(assemblies);
|
||||
|
||||
services.AddFluentValidationAutoValidation();
|
||||
|
||||
|
||||
//配置跨域
|
||||
services.AddCors(options =>
|
||||
{
|
||||
//更好的在Program.cs中用绑定方式读取配置的方法:https://github.com/dotnet/aspnetcore/issues/21491
|
||||
//不过比较麻烦。
|
||||
var corsOpt = configuration.GetSection("Cors").Get<CorsOptions>();
|
||||
string[] urls = corsOpt.Origins;
|
||||
options.AddDefaultPolicy(builder => builder.WithOrigins(urls)
|
||||
.AllowAnyMethod().AllowAnyHeader().AllowCredentials());
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
//redis
|
||||
IConnectionMultiplexer redisCon = ConnectionMultiplexer.Connect(conOpt.Redis);
|
||||
|
||||
services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.ConnectionMultiplexerFactory = () => Task.FromResult<IConnectionMultiplexer>(redisCon);
|
||||
});
|
||||
services.AddSingleton<IDistributedLockFactory>(sp =>
|
||||
{
|
||||
var connection = sp.GetRequiredService<IConnectionMultiplexer>();
|
||||
// 这里可以配置多个 Redis 节点提高安全性,单机运行传一个即可
|
||||
return RedLockFactory.Create(new List<RedLockMultiplexer> { new RedLockMultiplexer(redisCon) });
|
||||
});
|
||||
|
||||
|
||||
services.AddSingleton(typeof(IConnectionMultiplexer), redisCon);
|
||||
|
||||
|
||||
services.Configure<ForwardedHeadersOptions>(f =>
|
||||
{
|
||||
f.ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.All;
|
||||
});
|
||||
|
||||
services.AddSwaggerGenOpt();
|
||||
|
||||
services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
// 将枚举转换为字符串的转换器
|
||||
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user