添加项目文件。

This commit is contained in:
2026-05-09 17:06:30 +08:00
parent c60f5fe117
commit 720ef957d4
378 changed files with 14843 additions and 0 deletions
+33
View File
@@ -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;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace IM.InitCommon
{
public class ConnectionStringOptions
{
public string DefaultConnection { get; init; }
public string Redis { get; init; }
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace IM.InitCommon
{
public class ConsulOption
{
public string Url { get; private set; } = "http://192.168.5.100:8500";
}
}
+7
View File
@@ -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;
}
}
}
+57
View File
@@ -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;
}
}
}
+29
View File
@@ -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;
}
}
}
+36
View File
@@ -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);
});
});
}
}
}
+11
View File
@@ -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; }
}
}
+51
View File
@@ -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
}
}
+42
View File
@@ -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());
});
}
}
}